Skip to main content

SeqDev Actions

caution

Actions are a beta feature under active development. They are available for use in SeqDev, but their APIs and UI may continue to change as the feature matures. Please let us know if you have feedback on their future development.

Actions are custom tasks that developers can write and upload to a SeqDev Sequencing workspace, in order to traceably automate operations on the sequences and files within the workspace. Actions may read from and write to the contents of the workspace, and may communicate with other services to provide things such as translation, static checking or compilation. More info about this decision and its intentions can be found in ADR-0101 - SeqDev Actions.

This document describes the current state of the Actions feature as it exists today.

Using Actions

This section demonstrates how to use Actions in the UI and assumes you already have an action file to use - you can download this demo action to use as an example, or follow instructions in the "Writing an Action" section below to build your own.

SeqDev actions live within the context of a sequencing workspace, so to begin, go to the Sequencing page and create a new workspace. More info on workspaces, dictionaries and parcels can be found in the Sequence Editor docs. With your workspace open, open the Actions view. This will show you the actions associated with your workspace, and all of the past runs of those actions - currently both empty lists.

Once you're on the Actions page, click "New Action", give it a name (test-action) and upload the action.js file for your action (the provided example, or the output of your build step). If this completes successfully, you should see your new action appear in the list on the left side of the page. This also creates the first version of the action, v0. Click on the row to see details about your action:

  • The Runs tab shows you all past runs of this action, currently none
  • The Configure tab gives you access to action metadata, saved settings values, action-level archiving, and version management
  • The Code tab shows you the compiled source code of your action, ie. the contents of action.js, for a selected version

The rest of these instructions will assume you're using the example action - modify them as needed for your own settings/parameters. The example action attempts to make a request to an external URL and return the response as its result. Go to the Configure page and, for the setting called externalUrl, enter https://api.github.com. This will be used as the base URL for requests made by this action. Click Save to save your settings.

Now we're ready to run the action for the first time - click the "Run Action" button in the top right of the page. This opens a modal dialog with a form for entering parameters for this run. Like settings, parameters are options defined by the action - but unlike settings, they are expected to be provided by the user on each run, rather than saved and persisted for all future runs. Select the version to run, fill in any required parameters, and click "Run" to start the run. By default, SeqDev runs the latest non-archived version of the action. For the example action, enter repos/NASA-AMMOS/plandev for the urlPath parameter. The example action appends urlPath to externalUrl to form a complete URL to request.

This will take you to the action run detail page for this run, which will show information about the status of this particular run. After a few seconds, you should either see a green "success" status with results showing information about the plandev repo from the GitHub API, or a red "failure" status. If you see a failure, check to make sure your settings and parameters were set correctly.

Finally, you can go back to the main Actions page for your workspace, which should show your just-completed run in the list and gives you access to the list of all past runs, for purposes of traceability. Each run saves the settings and parameters used for the run, as well as any results, logs and errors encountered during the run.

Updating and archiving actions

After an action has been created, you can update its implementation by opening the action detail page and clicking "Upload New Version". This uploads a new action.js file and creates a new version of the same action instead of creating a separate action entry. New runs use the latest non-archived version by default, while previous runs remain linked to the specific version that produced them.

Action versions can be archived from the version list in the Configure tab or from the version selector in the Code tab. Archiving a version hides it from normal run selection and prevents it from being used for new runs through the Actions UI, but keeps the version and its previous run history available for traceability. If all versions are archived, the action cannot be run from the UI until a version is unarchived.

You can also archive the entire action from the Configure tab. This prevents users from running the action through the Actions UI and prevents new versions from being uploaded, while preserving the action, its versions, and previous runs. Users with permission to update action definitions can unarchive archived actions or versions.

Running actions on workspace files

Actions can declare file or sequence parameters so they can be run directly from selected files in the workspace. If an action has a compatible file, fileList, sequence, or sequenceList parameter, SeqDev can pre-fill that parameter from the active file or current file selection. Use primary: true on the parameter when an action has more than one file-like input and you want to specify which one should receive the selected workspace files.

For file and fileList parameters, the optional pattern field restricts which files can be used as input. For example, { type: "fileList", pattern: "*.json" } accepts only JSON files.

Writing an Action

Actions must be written in Javascript or Typescript, though other languages may be supported in the future. If you're unfamiliar with working on JS projects, it's a good idea to use an IDE such as VSCode or Webstorm for editing code.

We provide two resources for developers who want to write a new action:

We recommend starting a new action by forking or copying the aerie-action-template repository and using it as a starting point. Once you have a local copy, cd to it and run the following commands:

  • nvm use - (optional, but ensures you're using the correct node version. You may have to install NVM first)
  • npm install to install dependencies
  • npm run build to make sure you are setup correctly to build

Files in the /dist folder are generated and should not be modified directly - the action's source files are in the /src directory. Open src/index.ts in your code editor to start working on your action. You'll see at the top of the file the utilities that are imported from aerie-actions, which we'll discuss below.

Action parameters and settings

Actions may define two types of configuration which may be provided by the user to affect the action's behavior: parameters and settings. In both cases, the action developer defines the names and types of expected parameters/settings in the action code, and the user may provide values for them at runtime. The difference between them is that the values of settings are saved in the database and used in subsequent runs - to be used for persistent configuration such as the URL of a service which does not change often - while parameter values are expected to be provided by the user on every run of the action.

Definitions

You must define your parameters and settings as shown in the template structure: the variables must be called parameterDefinitions and settingDefinitions, but you should replace the contents with your desired parameter/setting names and their types. For example, the code:

export const parameterDefinitions = {
myName: { type: "string" },
myAge: { type: "int" }
} satisfies ActionParameterDefinitions;

defines two user-provided parameters, a string called myName and an integer called myAge. The full list of supported types can be found in the aerie-actions value schema definition.

Parameters and settings may also include a description. SeqDev shows descriptions as tooltips in the run action modal and action settings page.

Required and variant values

Parameters and settings can define required: true. SeqDev requires the user to provide a value in the UI, and the action server rejects a run if a required parameter or setting is undefined or an empty string. A required field can still provide a defaultValue, which will be used if the user does not override it.

Actions may also define variant parameters or settings. A variant behaves like an enum: the UI renders it as a dropdown, shows the label value to the user, and passes the selected key value into the action. The action server rejects values that are not listed in variants. Non-required variants may be left unset.

export const parameterDefinitions = {
commandFile: {
type: "sequence",
description: "Sequence file to process",
primary: true,
required: true
},
mode: {
type: "variant",
description: "How much processing to perform",
variants: [
{ key: "check", label: "Check only" },
{ key: "translate", label: "Translate" },
{ key: "compile", label: "Compile" }
],
defaultValue: "check",
required: true
},
outputName: {
type: "string",
defaultValue: "translated.seq",
required: true
}
} satisfies ActionParameterDefinitions;

If you are using Typescript, the next two lines should be left alone:

type MyActionParameters = ActionParameters<typeof parameterDefinitions>;
type MyActionSettings = ActionSettings<typeof settingDefinitions>;

as these will ensure that your function is provided with correct TS types for the parameters & settings you defined.

The action main function

Every action is expected to export a function called main, containing the main action code to be run when the action is invoked:

export async function main(parameters: MyActionParameters, settings: MyActionSettings, actionsAPI: ActionsAPI) {
/* action code here ... */
}

Actions are expected to be asynchronous, potentially long-running tasks - therefore main is expected to be an async function, or a function which returns a Promise which is resolved when the action is done running.

  • The first argument to main is parameters - the user-provided parameter values for this run, which will match the structure of your parameterDefinitions
  • The second argument is settings - the user-provided setting values, matching structure of settingDefinitions
  • The third argument is actionsAPI, which is an instance of the ActionsAPI class defined in aerie-actions. This is your hook for accessing (reading and writing) files and sequences in your workspace, as well as any other helper functions provided by the package.

Results, Errors and Logs

When your main function successfully completes, it should return an object that looks like:

return {
status: "SUCCESS",
data: someResults
};

The data value in this object is referred to as the result of your action, and may be any type of JS object, as long as it is serializable to JSON. Results are saved in the database and displayed on the action run page. In the future, these results may be able to be passed to other actions as parameters, enabling pipelines of actions.

If your action encounters a failure, you can indicate this in one of two ways:

  • You can throw an error eg. throw new Error('my message');. When making a call to a function that might fail, don't wrap it in a try/catch, just let it throw an error
  • Or you can return { status: "FAILED", data: someResults } with results related to the failure

Either of these is better than "swallowing" fatal errors by eg. catching them and logging them, as they will cause the run to properly be reported as a failure in the database and UI.

For non-fatal problems, or to log additional information, your action can use console.warn() and console.log() - the contents of the logs will be captured, saved and displayed alongside the run results.

danger

Be careful that you do not console.log() any sensitive information such as authentication tokens, passwords or opcodes, that you do not want to be persisted in the database in plaintext.

Using actionsAPI functions to read and write workspace files

The third argument passed to your main function is an instance of the ActionsAPI class defined in aerie-actions. This object contains a connection to the PlanDev database, and allows you to asynchronously read from and write to files in your workspace. More documentation for these functions can be found in the aerie-actions repo, and examples can be found in the aerie-action-template. The basic methods are:

// return a list of files in a workspace directory:
const files = await actionsAPI.listFiles('.');
// get the string contents of a file, given its workspace path:
const fileContents = await actionsAPI.readFile('my_seq.seq');
// write a new file, or pass true as the third argument to overwrite an existing file:
const writeResult = await actionsAPI.writeFile('output_seq.seq', 'NEW CONTENTS', true);

Actions can also read and update workspace file metadata:

const metadata = await actionsAPI.getFileMetadata('my_seq.seq');
await actionsAPI.setFileMetadata('my_seq.seq', { user: { status: 'checked' } });
await actionsAPI.unsetFileMetadata('my_seq.seq', ['user.status']);

See the ActionsAPI implementation for the full API surface.

caution

It is important to await these functions (and any other async functions you call), or use another method (eg. Promise.all) to ensure that the Promises they return are resolved or rejected before your main function returns/resolves. After the main Promise is resolved, the process running your Action may be terminated and any unprocessed results will be dropped.

Accessing secrets in actions

When an action is run, PlanDev automatically provides several secrets to your action via actionsAPI.config.SECRETS. These secrets are transient — they are passed at runtime and never stored in the database.

Built-in secrets (always available):

  • actionsAPI.config.SECRETS.authorization — the running user's JWT token (encoded)
  • actionsAPI.config.SECRETS.user — the running user's JWT payload (decoded JSON string)

Additional run context is available directly on actionsAPI.config:

  • actionsAPI.config.USER_ROLE — the running user's current Hasura role
  • actionsAPI.config.USERNAME — the running user's username
  • actionsAPI.config.ACTION_RUN_ID — the current action run ID

Secret parameters

Your action may define parameters with type: "secret", which causes them to be handled as transient secret-type parameters. Unlike other parameters, these will not be stored in the database and will be sent directly to the action only once, at the time you run your action. These are useful for passing some short-lived auth tokens to third-party services which may require them. For example:

export const parameterDefinitions = {
mySecretAuthToken: { type: 'secret' }
};

Use secret parameters judiciously - since their values are not stored, they cannot be audited after the action run to reproduce the same results later.

Environment variables

For security reasons, actions cannot access all environment variables that are set on the aerie_action container. However, for storing and passing long-lived secrets to your actions, you can make certain environment variables accessible by adding the prefix PUBLIC_ACTION_ to their name, for example:

PUBLIC_ACTION_MY_SECRET_API_KEY: "someTokenThatWillLastForAWhile"

These will be accessible in the action under the global process.env.

Forwarded browser cookies (optional, requires configuration):

If your deployment uses SSO or other cookie-based authentication, you can configure the action server to extract specific browser cookies and forward them to actions as secrets. This is useful when an action needs to authenticate with an external service under the user's identity.

To enable cookie forwarding, set the following environment variables:

ContainerVariableDescription
aerie_uiPUBLIC_ACTION_INCLUDE_CREDENTIALSSet to true to include browser credentials (cookies) in requests to the action server
aerie_actionACTION_COOKIE_NAMESComma-separated list of cookie names to forward (e.g. ssosession,other_cookie)
aerie_actionACTION_CORS_ALLOWED_ORIGINThe origin of the PlanDev UI (e.g. https://your-host.example.com).

Once configured, the specified cookies will be available under actionsAPI.config.SECRETS.cookies:

export async function main(parameters, settings, actionsAPI) {
const ssoToken = actionsAPI.config.SECRETS.cookies.ssosession;
// Use ssoToken to authenticate with an external service
}
caution

Do not console.log() secret values. Log contents are saved to the database in plaintext. Secret values are automatically redacted in logs, but return values from your action are not redacted — be careful not to include secret values in your return statement's data field.

Deployment notes

Cookie requirements: For a browser cookie to be forwarded, the environment variables noted above must be set, and the whitelisted cookies must also satisfy the browser's cookie-sending rules:

  • The cookie's domain must match the action server's domain (or be a parent domain, e.g. .jpl.nasa.gov)
  • If the cookie has the Secure flag, the action server must be served over HTTPS
  • If the cookie has SameSite=Strict, the request must be same-site
  • SameSite=Lax (the browser default) will send the cookie for same-origin requests and top-level cross-site navigations
  • httpOnly cookies are supported — this is a key benefit of this feature, since httpOnly cookies cannot be read by client-side JavaScript but are still sent by the browser with requests

Building your action

When you are ready to upload your action to a SeqDev workspace, run this build command:

npm run build

If successful, this should create a built file dist/action.js (in the action-template folder) which can be uploaded to SeqDev when you create an action. Whenever you change your action, you must re-run npm run build to rebuild it, then upload the rebuilt file as a new version from the action detail page.

Roadmap

SeqDev Actions are a beta feature under active development. If you have feedback or ideas for their future development, let us know.

Our near-term roadmap of upcoming changes to Actions includes:

  • More example actions & improved documentation
  • Patterns for testing actions - Users should be able to write tests for their action including mocking & spying on the aerie-actions library (expect(actions.readFile).toHaveBeenCalledWith('mySequence')).
  • Concurrency - We don't plan to fully solve the concurrency problem yet, but we should allow one action per workspace to run concurrently instead of one action per PlanDev deployment
  • Rethink bundling actions - Currently no way to upload an action with any other configs or scripts to call, you can only upload a single JS file. Should we rethink this to allow a user to give us eg. a node script that calls a python script, packed into a zip file? May also be nice for our action format to have a place to specify version, eg. a manifest file.

Our longer-term roadmap includes prospective features such as:

  • Triggers/hooks - a way to automatically run certain actions when certain things occur, such as a sequence being saved
  • Annotation files - workspaces should support "annotation files" which are associated with sequences, and contain line-numbered annotations to be displayed in the context of the sequence editor, to support eg. actions running a static checker and showing line-by-line results in the editor.
  • Concurrency - ability to run multiple actions in the same workspace at the same time, or allow the user to control whether or not this is possible
  • Action pipelines - connecting action runs together by piping the output results of one action into the input parameters of another.