Action concepts & best practices

Action concepts #

An action is defined by a Transposit Developer Platform application and implement a functionality end-to-end (E.g. merge a change on Jenkins, get the last 10 git commits, update the StatusPage incident page, etc.) An action application is broken up into two parts: Input and execute. Let's take a look at these operations.

Note: Our action template application follows this document and includes many of the best practices we reference. We recommend forking it to start building with.

Input operations #

Input operations define what information we are retrieving from the user and what the UI looks like. Make sure to add a parameter (located on the right side) named context in this operation. We recommend naming it the input_prompt operation.

They should also verify that all the required environment variables have a value defined. If not, it should return a meaningful error message.

Important: Use the Block Kit library to make it easier to form Slack Block Kit UI. You will need to add it as a connector in your action application. Under Code > Data connectors, search for Slack Block Kit. Select the "-- None --" operation and save. The Block Kit library docs also have examples you can use.

Note: If the Block Kit JSON is not valid, it will fail silently.

Execute operations #

Execute operations run when an action is run, manually or automatically. This is where most of the action logic is written and most external API calls are made.

We recommend split up your execute operations: execute, execute_wrapper, and operations for calling external integrations.

An execute operation generally contains code that:

  • Runs any necessary logic
  • Retrieves data from operations that call external integrations
  • Formats our response message so that it is useful for the user
  • Posts to the activity time and in Slack

An execute_wrapper operation generally contains code that:

  • Extracts user input
  • Calls the execute operation

execute_wrapper operations usually extract user input that was entered in the input_prompt operation out of the context parameter and pass it to the execute operation as individual parameters.

Example:

def execute_wrapper(params):
# Retrieve context parameter
context = params["context"]

# Retrieve value from input_prompt
# Note: if retrieving multiple values (i.e. multi_static_select, checkboxes)
# be sure to remove '[0]' from the end of the api.run command.
my_param_value = api.run(
"block_kit_lib.retrieve_input", {
"field_name": "my_text_field",
"context": context
})[0]

api.run("this.execute", {
"context": context,
"param1": my_param_value
})

An operation calling external integrations generally contains code that:

  • Makes calls to external APIs, sometimes based on user input

Typically, these are named based on their operations they are taking with an integration.

Retrieving metadata from integrators #

If you stored metadata in your integrator's integrator.response() operation that you would like to use in your action, you can find it in your context variable:

context = params['context']
integrator_response = context.get("integratorResponse")
metadata = integrator_response.get("metadata")

Example of integratorResponse:

{
"externalUrl": "alert_url",
"metadata": {
"app_name": "integrator_name",
"parsed_body": {
// Data you might want to use in your action
}
},
"name": "alert_title"
}

You can only store this data once in the integrator. You cannot change it in the action.

Retrieving results from input prompt #

Make sure to add a parameter (located on the right side) of type object named context in this operation.

The context parameter contains the input_prompt user input and other important information.

The block_kit_lib also contains a retrieve_text_input operation to help you retrieve user input. Here's an example that you might have in your execute_wrapper operation:

    context = params["context"]

# retrieve value from input_prompt
# Note: if retrieving multiple values (i.e. multi_static_select, checkboxes)
# be sure to remove '[0]' from the end of the api.run command.
my_param_value = api.run(
"block_kit_lib.retrieve_input", {
"field_name": "my_text_field",
"context": context
})[0]

Posting messages #

Use workflow.log.done(slack=output_blocks, metadata={}) to log results and mark the successful completion of an entire action run. For example, in your execute operation:

    output_blocks = api.run("block_kit_lib.markdown_text_section", {
"text": f"Results are: {results}"
})

workflow.log.done(
slack = output_blocks,
metadata = {}
)

There are five types of logs (seen under the Activities in main Transposit site):

  • Log
  • Status
  • Done
  • Warn
  • Fail

See more info about them in the Python operations doc.

Always include a done type.

Integrations #

Integrations (also known as data connectors in the Transposit Developer Platform) are how Transposit is able to connect your existing services and APIs together to interactive runbooks.

Authentication #

Authentication for an integration is located in the top left gear icon in Transposit, not in the Transposit Developer Platform.

You will need to check the box to enable the specific integration and either connect:

  • Authorization for actions that don’t require a specific user. Only one user on the team needs to provide this connection.
  • Authorization for actions that are performed as you

For authorizations for action applications that don’t require a specific user, you are done here after you connect.

If you building "actions that are performed as you" and want a user to connect to other services as themselves (E.g. so they can post to GitHub as themselves or create a Jira ticket with their credentials), you also need to do the following:

  1. Make sure the user creates a Transposit account with Slack login
  2. Make sure this account is added to the team
  3. If the team integration auth was set up before (I.e. “Authorization for actions that don’t require a specific user. Only one user on the team needs to provide this connection.”), make sure the application on the Transposit Developer Platform is set up to use the user based authentication under Users > User Configuration, as opposed to team based authentication. This box needs to be checked as seen below:
    Require users to authenticate with these data connections box

Lastly, no matter what authorization type you use, you can go to your Transposit Developer Platform action application and add corresponding data connector for the integration. You will be prompted to use an existing auth for the integration, which you will need to confirm.

Environment variables #

You can see the existing Transposit Developer Platform environment variables documentation here.

You will create your environment variables first in the Transposit Developer Platform, then when creating a action in Mission Control you can specify their values.

Think of environment variables as configuration of an specific instance of an action. You can have multiple runbooks using a single action application, but you might want them configured differently. This might be for different services, projects, regions, APIs, clusters, etc.

The code to get an env variable in the Transposit Developer Platform:

env.get("variable_name_here")

Overriding Data Connector Config #

This is not commonly needed, but only BaseUrl or Region can be overridden/managed at this time. In order to make use of this new feature, the action application needs to have declared that the Region (E.g. AWS Region) or Base URL "can be managed" or "can be overridden." Under Code > your specific data connector > Configuration > Edit:

Check box for Allow consumers to override this value

Note: After you have checked this box, the value for this configuration is set in the Integration section of the main Transposit site, not in the action itself.

Debugging errors while creating action applications #

Most of your debugging with happen by adding print() statements in Python and viewing them in the Monitor section on the left sidebar in the Transposit Developer Platform.

Note: Some server side errors may be only viewable by the Transposit team at this time, please feel free to reach out to support if you are experiencing an error and don't see anything in the Monitor section.

Best practices #

Sharing errors with users #

Currently, there are two ways to return errors to a user:

1. When input_prompt runs and show the error in the input modal

This case is good for validation on environment variables that does not require an API call.

Example: There is missing environment variable, and you want tell user what is missing and to go update it.

Here’s an example from the GitHub commit log action:

    blocks = []
repo_name = env.get("gh_repo_name")
if repo_name == None:
message = "Invalid settings, please verify that 'gh_repo_name' is correct."
message += " Values seen for gh_repo_name: " + str(repo_name)
blocks.append(api.run("block_kit_lib.text_section", {"text": message})[0])
return blocks

2. When execute runs and shows in the channel

This case is good for errors that involve API calls. It can also be used for validation.

Example: There is a bad API response and you want to let the user know something is wrong. Since the modal is already closed, you will need to share it in the channel and logs:

    workflow.log.fail(message="Restart VM workflow failed", metadata={})

See the catching API errors section below for an example.

Catching API errors #

Here’s an example from the GitHub commit log action:

    try:
commits = api.run("this.list_commits", {
"repo": repo_name,
"owner": owner_name
})
except ValueError as ex:
message = "Error from GitHub API: " + str(ex)
workflow.log.fail(
message = f"Error performing an action: {message}",
metadata = {}
)
return {}