Introducing Action Router

Handle multiple event types in a single GitHub Action by routing using GITHUB_EVENT_NAME and the payload action

If you’re building a GitHub Action that contains multiple triggers that have slightly different actions, you may find yourself writing code that looks like the following:

// We're working with PRs
if (tools.context.event == "pull_request") {
  if (tools.context.payload.action == "opened") {
    // Some logic for opened PRs
    handleOpenedPr(tools)
  }
  if (tools.context.payload.action == "labeled") {
    // Some logic for labelled PRs
    handleLabels(tools)
  }

  handleAnyPrEvent(tools)
}

// But we also want the label functionality to work for issues
if (
  tools.context.event == "issue" &&
  tools.context.payload.action == "labeled"
) {
  handleLabels(tools)
}

After I found myself writing code like the above repeatedly, I realised that what my more complex actions were missing was a router. Something to work out what the event type and subtype are and delegate to another method. I ended up building action-router which allows you to do the following:

router(
  {
    "issue.labeled": [handleLabels],
    "pull_request.opened": [handleOpenedPr],
    "pull_request.labeled": [handleLabels],
    pull_request: [handleAnyPrEvent],
  },
  [tools]
)

The router expects anything that’s callable, which means that so long as it can be called as a function you can require the code, define functions in the same file or even pass anonymous functions directly.

router({
  pull_request: [require("./allPr")],
  "pull_request.opened": [handleOpenedPr],
  "pull_request.labeled": [
    tools => {
      tools.github.removeLabel({ owner, repo, name })
    },
  ],
})

All of the methods that match the event type and subtype are run concurrently. This means that in the first router example both handleOpenedPr and handleAnyPrEvent would run together whenever a pull_request is opened. The results of these methods are returned as an array of promises, which means you can run the following:

const results = await router({
  "issue.labeled": [handleLabels],
})

// Results is an array of results. results[0] will be the return value of `handleLabels`
console.log(results)

I’ve used the router in anger on a few actions now and it’s definitely reducing the amount of boilerplate code I’m writing. If you’re interested in giving it a go, installation and usage instructions are available on GitHub


Published:
May 03, 2020

Category
Development
Topics
#github-actions

Want to learn how to build your own GitHub Actions?

Get The Book