Writing an Integration in YAML with Trigger Payloads, Inputs, and Outputs

Overview

In this quickstart, we examine a sample integration written in YAML to showcase the ability to import integrations defined as code.

In addition to examining writing integrations as code, this quickstart covers:

  • Invoking an integration with a webhook that contains a payload body
  • Referencing the trigger payload, both as an input variable for a component and in a code component
  • Knitting inputs and outputs throughout an integration to create more maintainable code

Prior to reviewing this quickstart, we recommend you check out our article on Defining Integrations as Code.

"Incomplete TODO Items" Integration Source Code

If you would like to import this integration yourself, or reference it in your own editor, download incomplete-todo-items.yaml. You can then run:

prism integrations:import --path path/to/incomplete-todo-items.yaml

What the "Incomplete TODO Items" Integration Does

This integration is triggered with an ID for a fictitious user, and writes out a list of incomplete "TODO" items for that user to a file in AWS S3. The integration is comprised of four steps (five if you include the trigger):

  1. The integration is triggered by a POST to a webhook URL. The POST includes a payload body that specifies a userId for the user we're concerned about.

    curl 'https://hooks.prismatic.io/trigger/INTEGRATION_ID' \
    --data '{"userId":5}' \
    --header "Content-Type: application/json" \
    --request POST
  2. Next, an HTTP GET step pulls https://jsonplaceholder.typicode.com/todos, which contains a list of about 200 TODO items of the form:

    [
    {
    "userId": 5,
    "id": 201,
    "title": "clean the car",
    "completed": false
    },
    ...
    ]
  3. After that another HTTP GET step uses the userId that was passed into the trigger to pull https://jsonplaceholder.typicode.com/users/userId, which returns something like this:

    {
    "id": 5,
    "name": "Chelsey Dietrich",
    "username": "Kamren",
    ...
    }

    The user's name and username are outputs of this step to be used by the next two steps.

  4. The next step is a code component action. The code filters the TODO items from step #1 to only include incomplete tasks for the userId that was passed in, formatted as a set of strings like this:

    Chelsey Dietrich needs to clean the car.
    Chelsey Dietrich needs to pay the bills.
    ...
  5. Finally, an S3 component action writes those TODO strings to a file in S3.

Now that we have a sense of what the integration does, let's dive into the code.

Referencing Configuration Variables

Configuration variables are defined under the requiredConfigVars header.

requiredConfigVars:
awsRegion: us-east-1
s3BucketName: my-s3-bucket-name

Those configuration variables can be referenced as inputs of steps:

- name: save_todo_list_to_s3
description: Save the user's incomplete tasks a file in S3
action:
key: putObject
componentKey: aws-s3
inputs:
awsRegion: configVars.awsRegion
bucket: configVars.s3BucketName
fileContents: outputs.list_items_for_user.all.incompleteTasks
objectKey: '"todoitems/list-for-" & outputs.get_user_info.userUsername & ".txt"'

Defining Output of a Trigger

The YAML that defines this integration's trigger reads:

trigger:
name: todos_trigger
description: Kicks off the integration
outputs:
userId: body.userId

We give the trigger the referential name name of todos_trigger. When an application invokes the webhook and passes in a JSON body payload of '{"userId":5}', that userId is output as a variable. Future steps can reference outputs.todos_trigger.userId, and in this example get a value of 5.

note

The outputs portion is not necessary, but saves typing in subsequent steps. Instead of using a named output, you could instead reference outputs.todos_trigger.all.body.userId, as the entire webhook payload of the trigger is accessible as output.

Trigger Output as Step Input

Using JSONata syntax and the userId variable from the trigger, a url of the form https://jsonplaceholder.typicode.com/users/5 is passed in as input.

- name: get_user_info
description: Get the name and username of the user and save it in an output
action:
key: httpGet
componentKey: http
inputs:
url: '"https://jsonplaceholder.typicode.com/users/" & outputs.todos_trigger.userId'
responseType: '"json"'
note

Note that the string portion of url is in double-quotes, while the variable reference is not quoted. The string and variable are combined with a single & using JSONata concatenation syntax.

Trigger Output in a Code Step

The userId output is also referenced in the third step, the code component, to filter TODO items only applicable to the specific user:

module.exports = async (
context,
{
todos_trigger: { userId },
get_all_todo_items: { all: items },
get_user_info: { userFullName },
}
) => {
const incompleteTasks = items
.filter((item) => item.userId == userId)

Defining Output of a Step

Like defining trigger output, you can define values to be output by steps. The get_user_info step, for example, outputs a fetched user's name and username.

- name: get_user_info
description: Get the name and username of the user and save it in an output
action:
key: httpGet
componentKey: http
inputs:
url: '"https://jsonplaceholder.typicode.com/users/" & outputs.todos_trigger.userId'
responseType: '"json"'
outputs:
userFullName: name
userUsername: username

Subsequent steps can reference outputs.get_user_info.userFullName and outputs.get_user_info.userUsername respectively.

note

Like trigger outputs, these named outputs are not necessary. You can remove them and instead reference outputs.get_user_info.all.name and outputs.get_user_info.all.username instead.

Named outputs are helpful to save you time and complexity when your the thing you want to reference is deeply nested. You would probably prefer to reference outputs.my_step_name.thing over outputs.my_step_name.all.foo.bar[0].baz.thing.

Defining Output in a Code Step

The code component also generates output values that can be referenced. The return value of the code component's function reads:

return {
incompleteTasks,
};

Subsequent steps can then reference outputs.list_items_for_user.all.incompleteTasks.

Step Output as Step Input

The last step utilizes the userUsername output from the get_user_info step to declare where to save a file in S3, and the incompleteTasks output from the list_items_for_user code component to declare the content of the file to be saved.

- name: save_todo_list_to_s3
description: Save the user's incomplete tasks a file in S3
action:
key: putObject
componentKey: aws-s3
inputs:
awsRegion: configVars.awsRegion
bucket: configVars.s3BucketName
fileContents: outputs.list_items_for_user.all.incompleteTasks
objectKey: '"todoitems/list-for-" & outputs.get_user_info.userUsername & ".txt"'

Step Output in a Code Component

Outputs of a step can be referenced by a code component. In the list_items_for_user step, outputs from the trigger, get_all_todo_items step, and get_user_info step are all referenced.

module.exports = async (
context,
{
todos_trigger: { userId },
get_all_todo_items: { all: items },
get_user_info: { userFullName },
}
note

The get_all_todo_items output, which is a JSON object with a outputs.get_all_todo_items.all property, has the .all portion reassigned to a variable items using JavaScript destructuring, since all is a reserved word in JavaScript.

userId, items, and userFullName are then all used in the code component to filter TODO items, and generate sentences of the form "Chelsey Dietrich needs to clean the car."

const incompleteTasks = items
.filter((item) => item.userId == userId)
.filter((item) => !item.completed)
.map(({ title }) => `${userFullName} needs to ${title}.`)
.join("\r\n");

Summary

Congratulations! Through this quickstart you built an integration in code, invoked the integration with a header that had a body payload, and knitted together trigger and step outputs and inputs. Next we recommend you try modifying the sample YAML file, or build a simple integration yourself from scratch!

Last updated on