# FlowDrop Trigger Module

Event-driven workflow triggering for FlowDrop. Automatically execute workflows when Drupal events occur (entity CRUD, user login/logout, etc.).

This module is now stable. Some internal classes are marked `@internal` and are not covered by backward compatibility promises. See the [BC Policy](../../docs/reference/api/bc-policy.md) for details.

## Overview

The `flowdrop_trigger` module enables event-driven workflow automation in FlowDrop. Trigger configurations are stored as dedicated config entities (`FlowDropTriggerConfig`) and are created when a trigger node is dropped in the workflow editor.

## Architecture

### Key Concepts

1. **FlowDropTriggerConfig Entity**: Stores trigger configuration (event type, conditions, orchestrator settings)
2. **Entity ID Format**: `{workflow_id}__{node_id}` - Unique per workflow node
3. **Immediate Creation**: Entity is created when node is dropped in editor (via API)
4. **Single Source of Truth**: All config lives in the entity, node only stores reference

### Flow

```
┌─────────────────────────────────────────────────────────────────────────┐
│  WORKFLOW EDITOR                                                        │
│                                                                         │
│  1. User drops "Trigger" node                                           │
│     │                                                                   │
│     ▼                                                                   │
│  2. POST /api/flowdrop/trigger-config                                   │
│     { workflow_id, node_id, event_type }                                │
│     │                                                                   │
│     ▼                                                                   │
│  3. FlowDropTriggerConfig entity created                                │
│     ID: my_workflow__node_abc123                                        │
│     │                                                                   │
│     ▼                                                                   │
│  4. Node stores: { trigger_config_id: "my_workflow__node_abc123" }      │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│  EVENT PROCESSING                                                       │
│                                                                         │
│  1. Drupal event occurs (entity.insert, user.login, etc.)               │
│     │                                                                   │
│     ▼                                                                   │
│  2. TriggerManager queries FlowDropTriggerConfig by event_type          │
│     │                                                                   │
│     ▼                                                                   │
│  3. Filter by conditions (entity_type, bundle, roles)                   │
│     │                                                                   │
│     ▼                                                                   │
│  4. Execute via orchestrator (sync/async)                               │
└─────────────────────────────────────────────────────────────────────────┘
```

## Features

- **Entity Events**: Trigger on entity insert, update, delete, presave
- **User Events**: Trigger on user login and logout
- **Cron Events**: Trigger on Drupal cron runs for scheduled workflows
- **Flexible Conditions**: Filter by entity type, bundle, user role
- **Orchestrator Settings**: Configure sync/async, pipeline mode, retry, priority
- **Dynamic Schema**: Event-type specific configuration forms
- **Admin UI**: List and edit trigger configurations

## Requirements

- Drupal 11.2+
- FlowDrop core modules:
  - `flowdrop`
  - `flowdrop_node_type`
  - `flowdrop_workflow`
  - `flowdrop_runtime`
  - `flowdrop_pipeline`

## Installation

```bash
drush en flowdrop_trigger
drush cr
```

## API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/flowdrop/triggers` | Create trigger config |
| GET | `/api/flowdrop/triggers/{id}` | Get config with schema |
| PATCH | `/api/flowdrop/triggers/{id}` | Update config |
| DELETE | `/api/flowdrop/triggers/{id}` | Delete config |
| GET | `/api/flowdrop/triggers/schema` | Get schema for event type |
| GET | `/api/flowdrop/triggers/event-types` | List available event types |

### Create Trigger Config

```json
POST /api/flowdrop/triggers
{
  "workflow_id": "my_workflow",
  "node_id": "node_abc123",
  "event_type": "entity.insert"
}

Response:
{
  "id": "my_workflow__node_abc123",
  "schema": { ... },
  "uiSchema": { ... },
  "values": { ... },
  "created": true
}
```

### Update Trigger Config

```json
PATCH /api/flowdrop/triggers/my_workflow__node_abc123
{
  "conditions": {
    "entity_types": ["node"],
    "bundles": ["article"]
  },
  "orchestrator_settings": {
    "type": "flowdrop_runtime:asynchronous",
    "priority": "high"
  }
}
```

## Event Types

### Entity Events

| Event | Description | Default Orchestrator |
|-------|-------------|---------------------|
| `entity.insert` | Entity created | Asynchronous |
| `entity.update` | Entity updated | Asynchronous |
| `entity.delete` | Entity deleted | Asynchronous |
| `entity.presave` | Before entity save | Synchronous |

### User Events

| Event | Description | Default Orchestrator |
|-------|-------------|---------------------|
| `user.login` | User logged in | Asynchronous |
| `user.logout` | User logged out | Asynchronous |

### Cron Events

| Event | Description | Default Orchestrator |
|-------|-------------|---------------------|
| `cron.run` | Drupal cron runs | Asynchronous |

Cron triggers are ideal for:
- Periodic data synchronization with external services
- Scheduled content publishing or unpublishing
- Automated cleanup and maintenance tasks
- Report generation at regular intervals
- Cache warming and optimization tasks
- Scheduled email digests and notifications

## Trigger Configuration Schema

```yaml
id: my_workflow__node_abc123
label: 'Article Creation Trigger'
description: 'Triggers when new articles are created'
workflow_id: my_workflow
node_id: node_abc123
event_type: entity.insert
conditions:
  entity_types:
    - node
  bundles:
    - article
orchestrator_settings:
  type: asynchronous
  pipeline_mode: new
  priority: normal
  timeout: 300
  retry:
    enabled: true
    max_attempts: 3
    delay: 60
    backoff: exponential
initial_data_mapping: {}
status: true
weight: 0
```

## Data Mapping with JSONPath

Trigger configurations support JSONPath expressions (RFC 9535) for extracting and mapping event data to workflow inputs.

### Configuration

```yaml
initial_data_mapping:
  # JSONPath expressions (start with $)
  user_email: '$.entity.mail.value'
  all_tags: '$.entity.field_tags[*].target_id'
  active_items: '$.entity.items[?(@.status == "active")]'
  
  # Property paths (legacy support)
  entity_title: 'entity.title'
  
  # Literal values
  source: 'literal:trigger_api'
```

### JSONPath Expression Examples

| Expression | Description |
|------------|-------------|
| `$.entity.title` | Entity title field |
| `$.entity.field_tags[*].target_id` | All tag IDs from entity reference |
| `$.entity.body.value` | Body field value |
| `$.original_entity.status` | Original entity status (for update events) |
| `$..target_id` | All target_id fields recursively |
| `$.entity.field_items[-1]` | Last item in a multi-value field |

### Available Context Data

The trigger context includes:

| Key | Description |
|-----|-------------|
| `entity` | Serialized entity data |
| `original_entity` | Original entity (for update/presave events) |
| `entity_type` | Entity type ID |
| `entity_id` | Entity ID |
| `bundle` | Entity bundle |
| `is_new` | Whether entity is new |
| `timestamp` | Unix timestamp |
| `trigger_config_id` | Trigger configuration ID |

## Permissions

| Permission | Description |
|------------|-------------|
| `administer flowdrop triggers` | Create, edit, delete trigger configurations |
| `view flowdrop triggers` | View trigger configuration list and details |

## Admin UI

Access trigger configuration list at:
`/admin/structure/flowdrop-trigger`

The admin interface provides:
- List of all trigger configurations
- Add new trigger configuration (manual creation)
- Edit existing trigger configurations
- Delete trigger configurations

Note: Triggers are typically created automatically when adding a Trigger node
in the workflow editor. The admin UI is provided for management and debugging.

## Settings Architecture

Trigger settings are organized into category-specific configuration pages for better UX and extensibility:

| Route | Config Object | Description |
|-------|---------------|-------------|
| `/admin/flowdrop/triggers/settings` | `flowdrop_trigger.settings` | General settings |
| `/admin/flowdrop/triggers/settings/general` | `flowdrop_trigger.settings` | Orchestrator, logging |
| `/admin/flowdrop/triggers/settings/entity` | `flowdrop_trigger.settings.entity` | Entity event settings |
| `/admin/flowdrop/triggers/settings/form` | `flowdrop_trigger.settings.form` | Form event settings |
| `/admin/flowdrop/triggers/settings/user` | `flowdrop_trigger.settings.user` | User event settings |

### Settings Forms

- **TriggerSettingsGeneralForm**: Default orchestrator, log level
- **TriggerSettingsEntityForm**: Entity types, bundles, event types
- **TriggerSettingsFormEventsForm**: Form IDs, include form values
- **TriggerSettingsUserForm**: User roles, privacy options
- **TriggerSettingsCronForm**: Cron interval, max workflows per run, logging

## Extending

### Adding Custom Settings Categories (Contrib Modules)

Contrib modules can add their own settings category tabs. Example for a "Commerce Events" category:

**1. Create the settings form:**

```php
<?php

namespace Drupal\my_module\Form;

use Drupal\flowdrop_trigger\Form\TriggerSettingsCategoryFormBase;
use Drupal\Core\Form\FormStateInterface;

final class TriggerSettingsCommerceForm extends TriggerSettingsCategoryFormBase {

  public function getFormId(): string {
    return "flowdrop_trigger_settings_commerce";
  }

  protected function getEditableConfigNames(): array {
    return ["my_module.flowdrop_trigger.settings.commerce"];
  }

  protected function getCategoryId(): string {
    return "commerce";
  }

  protected function getCategoryLabel(): string {
    return "Commerce";
  }

  public function buildForm(array $form, FormStateInterface $form_state): array {
    // Build your category-specific settings form.
    $form["enabled"] = $this->buildEnabledField($form_state);
    $form["event_types_wrapper"] = $this->buildEventTypesField($form_state);
    // Add custom settings...
    return parent::buildForm($form, $form_state);
  }

}
```

**2. Register the route (my_module.routing.yml):**

```yaml
my_module.trigger.settings.commerce:
  path: '/admin/flowdrop/triggers/settings/commerce'
  defaults:
    _form: '\Drupal\my_module\Form\TriggerSettingsCommerceForm'
    _title: 'Commerce Events'
  requirements:
    _permission: 'administer flowdrop triggers'
```

**3. Add the local task tab (my_module.links.task.yml):**

```yaml
my_module.config.triggers.commerce:
  route_name: my_module.config.triggers.commerce
  title: 'Commerce Events'
  base_route: flowdrop.config.triggers
  weight: 40
```

### Adding Custom Event Types

Create a new event type plugin:

```php
<?php

namespace Drupal\my_module\Plugin\FlowDropEventType;

use Drupal\flowdrop_trigger\Attribute\FlowDropEventType;
use Drupal\flowdrop_trigger\Plugin\FlowDropEventTypeBase;

#[FlowDropEventType(
  id: "my_custom.event",
  label: new TranslatableMarkup("My Custom Event"),
  description: new TranslatableMarkup("Triggered on custom event"),
  category: "custom",
  defaultOrchestrator: "flowdrop_runtime:asynchronous",
  defaultExecutionMode: "async",
)]
class MyCustomEvent extends FlowDropEventTypeBase {

  public function extractTriggerData(
    EntityInterface $entity,
    ?EntityInterface $originalEntity = NULL,
    array $context = [],
  ): array {
    return $this->getBaseTriggerData($entity);
  }

}
```

## Troubleshooting

### Triggers Not Firing

1. Verify the workflow is enabled (status = true)
2. Check the trigger config is enabled
3. Verify entity type and bundle conditions match
4. Check Drupal logs: `drush watchdog:show --type=flowdrop_trigger`

### Orphan Trigger Configs

Trigger configs that reference non-existent workflows are logged as warnings but not automatically deleted. Check logs for:
```
Trigger @trigger references non-existent workflow @workflow
```

## License

GPL-2.0-or-later
