Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation for customizing snap-in configuration page #63

Merged
merged 15 commits into from
Aug 14, 2024
2 changes: 1 addition & 1 deletion fern/docs/pages/references/inputs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ inputs:
- value-2
- value-3
is_required: true
default_value: value-1
default_value: [value-1]
ui:
display_name: Enum List Picker
```
218 changes: 218 additions & 0 deletions fern/docs/pages/references/snap-in-configuration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Customizing snap-in configuration pages

The DevRev snap-ins platform allows developers to define custom configuration pages for their snap-ins.

While the default configuration page automatically renders input fields for keyrings and inputs, there may be cases where a custom configuration page is more suitable:

- Improved user experience: Developers can design the configuration page to provide a more intuitive and streamlined experience for users setting up the snap-in.
- Advanced input handling: Custom configuration pages enable developers to handle complex input scenarios, such as fetching data from external systems to populate dropdown options or validating user input.
- Branding and styling: Developers can align the configuration page with their Snap-in's branding and style guidelines, ensuring a consistent look and feel.
codeon marked this conversation as resolved.
Show resolved Hide resolved

## Defining Custom Configuration Pages
codeon marked this conversation as resolved.
Show resolved Hide resolved

To create a custom configuration page for a Snap-in, developers need to define the following in the Snap-in manifest:
codeon marked this conversation as resolved.
Show resolved Hide resolved

```yaml
snap_kit_actions:
- name: org_snap_kit_action
function: org_snap_in_configuration_handler
- name: user_snap_kit_action
function: user_snap_in_configuration_handler

functions:
- name: org_snap_in_configuration_handler
description: Handler for processing organization configuration options.
- name: user_snap_in_configuration_handler
description: Handler for processing user configuration options.
- name: config_initializer
description: Generates the initial configuration options for both organization and user.

configuration_handler:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having separate functions for each of these is a bit expensive. Let's combine these into a single function in our example.

organization:
initializer: config_initializer
snap_kit_action_name: org_snap_kit_action
user:
initializer: config_initializer
snap_kit_action_name: user_snap_kit_action
```

The `configuration_handler` section in the manifest connects the functions responsible for generating and processing the custom configuration page.

- `config_initializer`: Generates the initial configuration options for both organization and user. It is called when the configuration page is first loaded.
- `org_snap_in_configuration_handler`: This function processes the organization configuration options. It is triggered when actions are performed on the organization configuration snap-kit.
- `user_snap_in_configuration_handler`: This function processes the user configuration options. It is triggered when actions are performed on the user configuration snap-kit.

## Configuration functions

The configuration functions should return a valid snap-kit JSON that defines the layout and elements of the custom configuration page. Here's an example of a snap-kit JSON:

```json
{
"snap_kit_body": {
"body": {
"snaps": [
{
"elements": [
{
"action_id": "user_snap_kit_action",
"action_type": "remote",
"elements": [
{
"element": {
"action_id": "select",
"action_type": "client",
"initial_selected_option": {
"text": {
"text": "Ticket",
"type": "plain_text"
},
"value": "ticket"
},
"options": [
{
"text": {
"text": "Ticket",
"type": "plain_text"
},
"value": "ticket"
},
{
"text": {
"text": "Conversation",
"type": "plain_text"
},
"value": "conversation"
}
],
"type": "static_select"
},
"type": "input_layout"
}
],
"submit_action": {
"action_id": "next",
"style": "primary",
"text": {
"text": "Next",
"type": "plain_text"
},
"type": "button",
"value": "next"
},
"type": "form"
}
],
"type": "card"
}
]
}
}
}
```

In this example, the snap-kit renders a dropdown select for choosing between "Ticket" and "Conversation," along with a "Next" button. When the "Next" button is clicked, the `user_snap_in_configuration_handler` function is invoked to process the user's selection.
codeon marked this conversation as resolved.
Show resolved Hide resolved
In the snap-kit action handler, the `event.payload.action.id` can be used to determine the form being submitted and call the `snap-ins.update` API to update the configuration.

## Update snap-in inputs (BETA)
codeon marked this conversation as resolved.
Show resolved Hide resolved

Updates the inputs of a snap-in based on the inputs defined in the snap-in configuration.

**Note: This endpoint is currently in BETA and may be subject to change in the future.**

### Endpoint

```
POST /internal/snap-ins.update
```

### Request Payload
codeon marked this conversation as resolved.
Show resolved Hide resolved

The request payload should be a JSON object with the following properties:

- `id` (string, required): The ID of the snap-in to update.
- `inputs_values` (object, required): An object containing the input values to update. The properties of this object should match the input names defined in the snap-in configuration.

Example payload:
```json
{
"id": "snap_in_id",
"inputs_values": {
"part_picker": "don:core:dvrv-us-1:devo/XXXX/product:XXXX",
"enum_list_picker": ["value-1", "value-2"]
}
}
```

In the example above, the `part_picker` and `enum_list_picker` are the input names defined in the snap-in configuration, and their corresponding values are provided in the `inputs_values` object.

### Response

#### Success response

- Status Code: 200 OK
- Content-Type: application/json

Response body:
```json
{
"data": {},
"message": "Snap-in updated successfully",
"success": true
}
```

#### Error response

If an error occurs while updating the snap-in, the response has the following format:

- Status Code: 4xx or 5xx
- Content-Type: application/json

Response body:
```json
{
"data": {},
"message": "Failed to update the snap-in. Err: {error details}, Status: {status code}",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the message field would be removed if we make the API beta.

"success": false
}
```

## Example usage

Here's an example of how to use the `/internal/snap-ins.update` endpoint using the provided code segment:

```typescript
async updateSnapInInputs(snapin_id: string, inputs_values: Record<string, any>): Promise<HTTPResponse> {
const payload: Record<string, any> = {
id: snapin_id,
inputs_values,
};
return this.updateSnapInGlobalVariable(payload);
}

async updateSnapInGlobalVariable(payload: Record<string, any>): Promise<HTTPResponse> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: globalVariable as a term is not exposed to the external developers. Let's use inputs?

try {
await axios.post(`${this.endpoint}/internal/snap-ins.update`, payload, this.configs);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reminder to update this to use devrevSDK?

return { data: {}, message: 'Snap-in updated successfully', success: true };
} catch (error: any) {
if (error.response) {
const err = `Failed to update the snap-in. Err: ${JSON.stringify(error.response.data)}, Status: ${error.response.status}`;
return { ...defaultResponse, message: err };
} else {
return { ...defaultResponse, message: error.message };
}
}
}
```

In this example, the `updateSnapInInputs` function takes the `snapin_id` and `inputs_values` as parameters. The `inputs_values` object should contain the input names and their corresponding values as defined in the snap-in configuration.

The function constructs the payload object with the `snapin_id` and `inputs_values`, and then calls the `updateSnapInGlobalVariable` function to make the POST request to the `/internal/snap-ins.update` endpoint.

If the request is successful, it returns a success response with a status code of 200 and a message indicating that the snap-in was updated successfully.

If an error occurs, it catches the error and returns an error response with an appropriate status code and an error message containing the error details.

Please keep in mind that this endpoint is currently in BETA, and its functionality or parameters may change in future updates.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud - Should we have some sort of subscribe to beta changes button in our documentation which can help us keep track of who all external parties which are using a beta feature? It can help us in the future when we need to track down who all are depending on this feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally we should be doing this based on the endpoint and our logging. But I can put a note here.

codeon marked this conversation as resolved.
Show resolved Hide resolved

For more details on the snap-kit JSON format and available elements, refer to the [DevRev Snap-kit documentation](/snapin-development/references/snapkit).
107 changes: 107 additions & 0 deletions fern/docs/pages/retry-mechanism.mdx

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any external developers which are waiting for the event-retry documentation to become public? My suggestion would be to keep this particular documentation internal and let our main snap-ins like Email, Slack etc. onboard to this mechanism first to confirm that everything works and is stable in PROD.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, removed retries doc for now since we are also revamping the backend approach.

Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Event reliability in DevRev snap-ins

The DevRev snap-ins platform offers event reliability features to ensure smooth and resilient event processing. This document provides an overview of these features and how developers can leverage them to build reliable snap-ins.

## Getting started

To start using the event reliability features in your Snap-ins, follow these steps:

1. Update your DevRev SDK to the latest version.
2. Define retryable errors using the `FunctionExecutionError` interface in your Snap-in code.
3. Configure the retry behavior in your Snap-in manifest.
4. Handle errors appropriately in your Snap-in function.

## Retryable errors

Snap-ins can define retryable errors using the `FunctionExecutionError` interface provided by the DevRev SDK. This enables the platform to automatically retry events that encounter intermittent or transient errors, improving overall reliability.

## Retry configuration

Developers can configure the retry behavior of their Snap-in functions using the Snap-in manifest. The following options are available:

```yaml
functions:
- name: function_name
config:
runtime:
max_retries: 5 # number of retries before failing the event
min_interval: 10 # interval in seconds between each retry

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Let's use a more realistic value

Suggested change
min_interval: 10 # interval in seconds between each retry
min_interval: 120 # interval in seconds between each retry

```

- `max_retries`: The maximum number of retries before marking the event as failed.
- `min_interval`: The minimum interval in seconds between each retry attempt. This interval may be adjusted based on the timeout of the corresponding function.

## Error handling

The DevRev snap-in platform handles errors based on the ordering guarantees of the snap-in function.

For snap-in functions with relaxed ordering, non-retryable errors are marked as failed, and the errored event is propagated to the DevRev platform for tracking. Retryable errors are automatically retried based on the specified retry configuration. If the maximum number of retries is exhausted, the errored events are moved to a dead-letter queue (DLQ) for further handling.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now all snap-in-functions have relaxed ordering. Mentioning this here might be confusing.

Suggested change
For snap-in functions with relaxed ordering, non-retryable errors are marked as failed, and the errored event is propagated to the DevRev platform for tracking. Retryable errors are automatically retried based on the specified retry configuration. If the maximum number of retries is exhausted, the errored events are moved to a dead-letter queue (DLQ) for further handling.
For snap-in functions with ordering, non-retryable errors are marked as failed, and the errored event is propagated to the DevRev platform for tracking. Retryable errors are automatically retried based on the specified retry configuration. If the maximum number of retries is exhausted, the errored events are moved to a dead-letter queue (DLQ) for further handling.


## Error interface

The DevRev SDK defines the `FunctionExecutionError` type to represent errors returned from the snap-in's run function. Developers can use this type to provide additional error details and indicate whether an error is retryable.

```typescript
class FunctionExecutionError extends Error {
/**
* Toggle to determine if the event should be retried or not. If not set or set to false,
* the event is not retried.
*/
retry: boolean;

/**
* Whether to retry the event payload with updated metadata
* that platform provides. Useful when the event payload is
* not in a state to be directly processed, and may need new
* keyrings/service account tokens or new inputs.
*/
refresh?: boolean;

constructor(message: string, retry: boolean, refresh?: boolean) {
super(message);
this.retry = retry;
this.refresh = refresh;
}
}
```

## Example usage

Here's an example of how to use the `FunctionExecutionError` in your Snap-in code:

```typescript
import { FunctionExecutionError } from '@devrev/typescript-sdk/dist/snap-ins/types';

export const run = async (events: any[]) => {
/*
Put your code here to handle the event.
*/
console.log('Events: ', JSON.stringify(events));

try {
// Your event processing logic here
// ...

// Retryable error
console.log('Retrying....');
const runtimeError = new FunctionExecutionError('Runtime Retryable Error', true, false);
throw runtimeError;

// Non-retryable error
// const runtimeError = new FunctionExecutionError("Runtime Non-Retryable Error", false, false);
// throw runtimeError;

} catch (error) {
if (error instanceof FunctionExecutionError) {
throw error;
} else {
// Handle other types of errors
// ...
}
}
};

export default run;
```

In this example, the Snap-in function's `run` method processes the events and can throw a `FunctionExecutionError` to indicate whether the error is retryable or not. The Snap-in platform handles the error based on the `retry` and `refresh` properties set in the error object.
9 changes: 9 additions & 0 deletions fern/versions/public.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ navigation:
- page: Snap-in resources
slug: snap-in-resources
path: ../docs/pages/references/snap-in-resources.mdx
- page: Snap components
slug: snap-components
path: ../docs/pages/references/snap-components.mdx
- page: Customizing snap-in configuration
slug: customizing-snap-in-configuration
path: ../docs/pages/references/snap-in-configuration.mdx
- page: Handling errors and retrying
slug: handling-errors-and-retrying
path: ../docs/pages/references/retry-mechanism.mdx
- page: Development best practices
slug: best-practices
path: ../docs/pages/best_practices.mdx
Expand Down
Loading