Skip to content

Commit

Permalink
Merge pull request #112 from contember/actions-up
Browse files Browse the repository at this point in the history
Actions docs up
  • Loading branch information
matej21 committed Jul 11, 2023
2 parents 30e2621 + 4029f5d commit 0469fc1
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 69 deletions.
115 changes: 71 additions & 44 deletions docs/reference/engine/actions/definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ While working with Actions in Contember, please remember that any addition or mo

## Defining a Watch Action

To define an Action, you employ Contember's decorator syntax. The `@watch` decorator is attached to the entity class you aim to observe for changes.
The watch action in Contember Actions allows you to track and monitor specific changes within an entity. When you define a watch action, Contember automatically keeps track of the creation, deletion, and updates of the entity, as well as any changes made to the watched fields and relations.

#### Example: Basic structure of an Action definition:
To define a Watch Action, you employ decorator syntax. The `@watch` decorator is attached to the entity class you aim to observe for changes.

#### Example: Basic structure of a Watch Action definition:

```javascript
import { SchemaDefinition as def, ActionsDefinition as actions } from '@contember/schema-definition'
Expand All @@ -37,59 +39,24 @@ export class YourEntity {
- `webhook`: Provides the URL to which the webhook notification will be directed when changes occur. [see advanced configuration](#webhook-configuration)
- `selection` (optional): Specifies the selection that will be dispatched in a payload.

### Observing Fields and Relations

The `watch` property allows you to define the fields and relations that you wish to observe for changes. It is possible to watch individual fields of the entity and also specify nested fields within related entities. This facilitates the tracking of changes in fields such as strings, numbers, booleans, or timestamps, along with changes in relationships between entities.

### Payload and Selection

By default, the payload that is sent to the webhook encapsulates the changes that triggered the Action. However, you can personalize the payload by defining a `selection` property within the `@watch` decorator. The `selection` property enables you to incorporate specific fields in the payload, offering fine-grained control over the data dispatched to the webhook.

Example: Defining a selection within a watch

```typescript
import { SchemaDefinition as def, ActionsDefinition as actions } from '@contember/schema-definition'

@actions.watch({
name: 'order_watch',
watch: `
status
customer {
name
email
}
`,
webhook: 'https://example.com/order/updated',
selection: `
status
customer {
name
}
`,
})
export class Order {
// Entity properties and relationships
}
```

## Defining a Trigger Action

Trigger Actions in Contember serve as a lower-level alternative to watch-based Actions, giving you the ability to selectively observe specific operations on an entity. These operations can include creation, deletion, or updates to specified fields. By providing precise control over which operations initiate your webhook, Trigger Actions let you concentrate on tracking the specific changes that matter to your application.

To define a Trigger Action, you attach the `@trigger` decorator to the entity class you wish to monitor.

Here is the structure of the Trigger Action syntax:
#### Example: Basic structure of a Trigger Action definition:

```typescript
import { SchemaDefinition as def, ActionsDefinition as actions } from '@contember/schema-definition'

@actions.trigger({
name: 'action_name',
create?: boolean,
delete?: boolean,
update?: boolean | readonly string[],
selection?: Actions.SelectionNode | string,
webhook: 'webhook_target'
create: true,
delete: true,
update: ['field_to_watch'], // or "true"
selection: 'optional_selection_for_payload',
webhook: 'webhook_url'
})
export class YourEntity {
// Entity properties and relationships
Expand Down Expand Up @@ -127,6 +94,37 @@ export class Book {

With the provided Trigger Action configuration, the webhook will be invoked whenever a new `Book` entity is created. The payload dispatched to the webhook will include the defined fields (`title` and `author.name`), allowing you to execute custom actions or alert external systems about the creation event.

## Payload and Selection

By default, the payload that is sent to the webhook encapsulates the changes that triggered the Action. However, you can personalize the payload by defining a `selection` property within both `@watch` and `@trigger` decorators. The `selection` property enables you to incorporate specific fields in the payload, offering fine-grained control over the data dispatched to the webhook.

Example: Defining a selection within a watch

```typescript
import { SchemaDefinition as def, ActionsDefinition as actions } from '@contember/schema-definition'

@actions.watch({
name: 'order_watch',
watch: `
status
customer {
name
email
}
`,
webhook: 'https://example.com/order/updated',
selection: `
status
customer {
name
}
`,
})
export class Order {
// Entity properties and relationships
}
```

## Webhook Configuration

The `webhook` property determines the URL where the webhook notification will be dispatched. This could be an external service or an endpoint within your own application that processes the webhook payload.
Expand All @@ -136,7 +134,7 @@ The `webhook` property determines the URL where the webhook notification will be
Instead of defining a simple string for the `webhook` property, you have the option to pass an object that allows for a more detailed configuration of the webhook. This feature gives you the ability to set additional headers, specify timeouts, manage retry attempts, and adjust the batching of webhook requests. Below is an example demonstrating how to leverage these advanced options:

```javascript
import {SchemaDefinition as def, ActionsDefinition as actions} from "@contember/schema-definition"
import { SchemaDefinition as def, ActionsDefinition as actions } from "@contember/schema-definition"

@actions.watch({
name: 'book_watch',
Expand Down Expand Up @@ -238,3 +236,32 @@ In this sample, a separate webhook target named `myOrderUpdateTarget` is defined
To link the webhook target with an Action, the `target` property within the `@watch` decorator instead of the traditional `webhook` property. This facilitates referencing the shared webhook target for the specified Action. The remainder of the configuration, including the `name` and `watch` properties, stays consistent.

This method also permits the disabling of the watch while retaining the target definition, enabling any pending events to be dispatched even when the watch is inactive.

## Events Priority

Contember Actions facilitates event prioritization in `watch` and `trigger` actions. Considering that events from various actions are stored and processed in a single queue, the ability to assign priority levels to certain events is significant. Higher priority ensures these events are processed ahead of others, which is especially beneficial for critical operations or time-sensitive tasks.

To assign priority for a `watch` or `trigger` action, include the `priority` property in the action configuration. The `priority` value should be a positive integer, with higher values indicating higher priority. Consequently, events associated with a higher priority value will precede those with lower priority values during processing.

Below is an example of assigning priority to a `watch` action:

```typescript
import { SchemaDefinition as def, ActionsDefinition as action } from "@contember/schema-definition"

@action.watch({
name: 'book_watch',
watch: `
title
tags {
name
}
`,
webhook: 'https://example.com/webhook',
priority: 2
})
export class Book {
// Entity definition
}
```

In this example, the `watch` action 'book_watch' is assigned a priority level of 2. Therefore, events triggered by changes in the 'title' or 'tags' fields of the 'Book' entity will have a processing priority level of 2 in the event queue.
107 changes: 85 additions & 22 deletions docs/reference/engine/actions/invocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ After a watched event on a monitored entity is registered, it is queued within t

## Batching

To optimize efficiency, multiple events targeting the same webhook can be batched together. By default, a batch consists of up to 20 events, but this value can be adjusted by configuring the `batchSize` property in the webhook definition. Batching allows the worker to process multiple events in a single invocation, reducing the overhead of individual HTTP requests.
To optimize efficiency, events targeting the same webhook can be batched together, allowing for processing multiple events in a single invocation. By default, each batch contains a single event, and the event payload is wrapped in an array. However, you can adjust the `batchSize` property in the webhook configuration to specify the maximum number of events per batch.

It's important to note that when processing a batch, all events within the batch are considered either successful or failed based on the HTTP response code. Currently, partial success for individual events within a batch is not supported. If any event in the batch fails, the entire batch is considered failed.

## Fetching events

Expand All @@ -30,37 +32,35 @@ By default, Contember appends the following headers to the webhook request:

These headers identify the source of the webhook request and specify the format of the payload.

## Timeout and Retry Handling

A timeout is enforced for webhook completion to ensure timely processing. By default, the timeout is set to 30 seconds, but it can be adjusted in the webhook configuration (using `timeoutMs` prop). If a webhook fails to respond within the specified timeout, all events in the batch are marked as "retrying." Similarly, if the webhook does not respond with an HTTP status code in the 2xx range, the events are also marked as "retrying." This mechanism helps to handle failures and retries automatically, ensuring robust event processing.

Contember follows a retry strategy for events marked as "retrying." By default, the initial repeat interval between retry attempts is set to 5,000 milliseconds (can be changed using `initialRepeatIntervalMs` webhook prop), and it follows an exponential backoff strategy for subsequent retries. The interval between retries doubles with each attempt until the maximum number of attempts is reached. The maximum number of attempts is set to 10 (`maxAttempts` prop in webhook configuration), meaning
Contember will attempt to send the webhook request a maximum of 10 times before considering it as a failure.
## Request payload

## Payload

When a batch of events is dispatched and the corresponding webhook is invoked, the payload sent to the webhook contains a field named `events`. This field holds an array of event payloads representing the batched events. Each event payload follows a specific structure based on the type of event triggered.
When a batch of events is dispatched and the corresponding webhook is invoked, the payload sent to the webhook contains a field named `events`. This field holds an array of event payloads representing the batched events. Each event payload follows a specific structure based on the type of event triggered.


#### Example: webhook body
```json5
{
"events": [
{
"id": "...",
"entity": "...",
"type": "watch",
// ...
},
/// other events
]
"events": [
{
"id": "...",
"entity": "...",
"type": "watch",
// ...
},
/// other events
]
}
```

:::note
Even when a batch is configured to contain only a single event, it is still sent in the `events` field as an array with a single item. This consistent structure allows for unified handling of batched events, ensuring consistent processing logic regardless of the number of events in the batch.
:::

### Watch Event Payload

A `watch` event payload represents a change in the watched entity and provides detailed information about the change. Here is the structure of a `watch` event payload:

- `id` (string): The unique identifier of the entity event.
- `id` (string or int): The unique identifier of the entity event.
- `entity` (string): The entity type of the watched entity.
- `events` (array of objects): An array of event payloads representing the changes in the watched entity. Each event payload has the following properties:
- `id` (string): The unique identifier for the event.
Expand All @@ -71,6 +71,7 @@ A `watch` event payload represents a change in the watched entity and provides d
- `trigger` (string): The name of the trigger associated with the event.
- `operation` (string): The operation type of the event, which is set to `watch` for a watch event.
- `selection` (object): Custom payload defined by `selection` on a watch definition
- `meta` (object): [see event meta](#event-metadata)

#### Example: payload of a `watch` event payload for a `Book` entity:

Expand Down Expand Up @@ -100,11 +101,12 @@ In this example, the `watch` event payload represents an update operation on a `

Trigger event payloads represent payloads for events invoked by `trigger` and contains individual basic events.

- `id` (string): The unique identifier for the entity.
- `id` (string or int): The unique identifier for the entity.
- `entity` (string): The entity type associated with the event.
- `operation` (string): The type of operation performed on the entity. Possible values are `create`, `update`, `delete`, `junction_connect`, or `junction_disconnect`.
- `selection` (object, optional): Additional information about the selected fields in the event, if specified in the event configuration.
- [other fields based on operation type](#basic-events)
- `meta` (object): [see event meta](#event-metadata)
- [other fields based on operation type](#basic-events)

Here's an example of a basic event payload for an `update` operation on a `Book` entity:

Expand All @@ -124,6 +126,17 @@ Here's an example of a basic event payload for an `update` operation on a `Book`

In this example, the basic event payload represents an `update` operation on a `Book` entity. It includes the updated values of the `title` and `author` properties. The `operation` is set to `update`, and the `id` identifies the specific book entity. The `selection` and `path` properties are optional and provide additional context or information about the event within the entity graph.

## Event Metadata

Each event within the webhook payload contains metadata that provides essential information about the event. The event metadata, available under the `meta` field, includes the following properties:

- `eventId` (UUID string): A unique identifier for the event. This identifier can be used to track and reference the event throughout your system.
- `transactionId` (UUID string): The identifier for the transaction associated with the event. This can be helpful for managing the event within a transactional context.
- `createdAt` (ISO 8601 string): The timestamp indicating when the event was created. It represents the moment when the event was initially recorded.
- `lastStateChange` (ISO 8601 string): The timestamp of the last state change for the event. It indicates when the event's state was last modified or updated.
- `numRetries` (int): The number of times the event has been retried. This count can help you track the number of retry attempts made for the event.
- `trigger` (string): The name of the trigger or watch that caused the event.
- `target` (string): The name of target associated with the event.

### Basic events

Expand Down Expand Up @@ -167,3 +180,53 @@ export type JunctionDisconnectEvent = {
inverseId: PrimaryValue
}
```
## Processing timeout
A timeout is enforced for webhook completion to ensure timely processing. By default, the timeout is set to 30 seconds, but it can be adjusted in the webhook configuration (using `timeoutMs` prop). If a webhook fails to respond within the specified timeout, all events in the batch are marked as "retrying."
## Response Processing
When processing the response received from a webhook invocation, Contember follows specific rules to determine the success or failure of the batched events. Here's an overview of the response processing rules:
1. **Not-OK Response Status**: If the HTTP response status is considered not OK, meaning it falls outside the range of 2xx, the entire batch is considered unsuccessful. The response received is stored in the `log` field of each event, providing information about the failure.
2. **OK Response Status with Empty Body**: If the HTTP response status is OK (2xx) and the response body is empty, the entire batch is considered successful.
3. **OK Response Status with Non-Empty Body**: If the HTTP response status is OK (2xx) and the response body is not empty, the response is expected to contain a JSON object with a `failures` field.
- **Invalid Response Structure**: If the response structure is invalid, either due to it not being valid JSON or not matching the expected format, the entire batch is considered unsuccessful.
- **Invalid `eventId`**: If the response contains an invalid `eventId` that does not match any event in the batch, the entire batch is considered unsuccessful.
- **Valid Response Structure**: If the response structure is valid and the `eventId` matches an event in the batch, the events specified in the `failures` field are marked as unsuccessful, while the remaining events are marked as successful.
Following the processing of the response, the standard retry mechanism is applied to the events that were marked as unsuccessful. This ensures that the unsuccessful events are retried according to the configured retry logic, allowing for subsequent attempts to process them successfully.
Example: webhook response payload with the `failures` field indicating event failures:
```json
{
"failures": [
{
"eventId": "f4f0a97d-7850-4add-8946-a1ce016306ce",
"error": "Invalid input"
},
{
"eventId": "a2b1c3d4-5678-90e1-2345-678f9g0h12i",
"error": "Service not available"
}
]
}
```
In this example:
- Two events within the batch are marked as failures.
- The `eventId` field uniquely identifies each failed event. This id matches the `eventId` in a `meta`.
- The `error` field provides additional information about the cause of the failure, such as an error message or a specific reason for the event processing failure.
## Retries
Contember follows a retry strategy for events marked as "retrying." By default, the initial repeat interval between retry attempts is set to 5,000 milliseconds (can be changed using `initialRepeatIntervalMs` webhook prop), and it follows an exponential backoff strategy for subsequent retries. The interval between retries doubles with each attempt until the maximum number of attempts is reached. The maximum number of attempts is set to 10 (`maxAttempts` prop in webhook configuration), meaning Contember will attempt to send the webhook request a maximum of 10 times before considering it as a failure.
2 changes: 1 addition & 1 deletion docs/reference/engine/actions/managing.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Please note, the Actions feature is only available starting from version 1.3, wh
The Actions Management API in Contember provides a GraphQL interface for managing Action events and variables in your project. You can access the API by making GraphQL requests to the `/actions/<project-name>` endpoint, where `project-name` represents the name of your Contember project.

## Permissions
You must include a Bearer token in Authorization header. To interact with the Actions Management API, you need the "admin" role for your Contember project. It is not yet possible to manage these permissions.
You must include a Bearer token in Authorization header. To view and setup variables, you need either `admin` or `deployer` role. To interact with other Actions Management APIs, the `admin` role is required. It is not yet possible to configure these permissions.

## Managing Variables

Expand Down
Loading

0 comments on commit 0469fc1

Please sign in to comment.