Why build a bot?

When processes get complex in Asana there can begin to be work about work. This could be happening to you (or someone you love) if you find yourself spending time doing repetitive work such as triaging tasks, reminding people to do something, or adding/removing followers as you move a task through a workflow.

What we’re going to build

In this guide, we will build a simple triage bot that will assign tasks. This is a common Asana use case with support inboxes or request projects.

If you want to skip ahead and see the code for the triage bot, it’s on Github in the Javascript folder of our devrel-examples repo: https://github.com/Asana/devrel-examples.

For this guide, let’s suppose a design team has a requests project where people from the marketing team fill out an Asana form to request graphics from the design team. The form creates a task in the design requests project that needs to be assigned to a designer.

Our triage bot will gather all unassigned tasks in the design request project and then randomly distribute the requests across a group of designers.

For the purposes of this guide, we will keep it this simple, however, you could add more complex logic to your bot. For instance, you could check custom field values on the request task to see what type of request it is (e.g. video, graphic, logo, etc.) and then assign it to the designer that has those skills. You could go even further and check the designers workload to see who currently has the least amount of work already assigned to them (this could be determined by a point value for tasks assigned to them in the project). You could then have the bot ping the design request task as it approaches the due date to ensure that the designer will have it completed on deadline.

Helpful links

Before we get started, here are some helpful links for building on the Asana API:

Create your bot user in Asana

Create a new Asana account for your bot (instructions for inviting users). You want to create a distinct Asana account for your bot because any action it takes in Asana will be attributed to this user. Give your bot a name and photo that will be recognizable to users in Asana that encounter it. Note that if your bot is a guest member in Asana that it will need to be added to every project you need it to work in. Bots based on guest Asana accounts will also not have access to some API features such as defining new custom fields or modifying their settings.

Authenticating your bot

We will authenticate our bot using a Personal Access Token (PAT). Log in to the Asana account that will be used for the bot and navigate to the developer console. You can get to your dev console by either using this URL https://app.asana.com/-/developer_console or from within Asana by clicking your photo icon in the upper right of Asana -> My Profile Settings -> Apps -> Manage Developer Apps.

Next, click “+ New access token” and follow the instructions to get your token. Treat this token like a username and password. Don’t share it with anyone and never publish it to a public repository. I like to save my PAT as an environment variable (here are instructions on how to do this on Mac). For this guide, I’ve saved a PAT as an env variable called triage_bot_pat.

Create an Asana sandbox

Before we start coding, create a project in Asana to use as a sandbox. While not required, I like to set the project to private while developing. To get some users in the project, add your main Asana user as well as your bot account. You could also invite a personal email as a guest user.

Choose an Asana client library

The Asana API has SDKs in several popular languages. For most developers, we recommend using one of our client libraries because they help with some of the complexities of using an API such as authentication, pagination, and deprecations.

For this guide, we will use the Asana Node client library, however, you can follow along in any language of your choice.

Each library has an examples folder in addition to the readme, which can be helpful for getting started. The methods for each resource can be found in the “gen” folder of each client library (e.g. node-asana/lib/resources/gen/).

Let’s start coding!

We will use a very basic file structure for the purposes of this guide. Start by making a project directory, moving into it, and running npm init:

$ mkdir triage-bot
$ cd triage-bot
$ npm init

Install the Node Asana client library and create config and app files:

$ npm install asana
$ touch config.js app.js

Add gids (global ids) for the workspace, design request project, and designers that will be assigned requests (note that all ids in Asana should be strings). You can get a project’s gid from its URL in the Asana web product (e.g. the structure of links for a task is www.asana.com/0//). Similarly, you can get user’s gid from the URL of their task list (i.e. click on their name in Asana). To get your workspace gid(s), go to https://app.asana.com/api/1.0/users/me/workspaces.

let config = {
  workspaceId: "15793206719",
  designRequestProjectId: "180350018127066",
  //gids of designers who are fulfilling design requests
  designers: ["180015866142449", "180015866142454", "180015886142844"]
}

module.exports = config;

Next, let’s get started on our app.js file. Include the Asana node client library and the config file:

let asana = require('asana');
let config = require('./config');

Get your access token and use it to create an Asana client. At the time of writing this guide, the Asana API is going through two deprecations (moving to string gids and changing how sections function). You can learn about our deprecations framework in our docs. To prevent my app from breaking when the deprecations are finalized, I’m passing headers to enable the new API behavior for string gids and sections. We will also set a delay to determine how quickly our parallel requests are sent.

// Get personal access token (PAT) from environment variables.
const accessToken = process.env.triage_bot_pat;

const deprecationHeaders = {"defaultHeaders": {"asana-enable": "new_sections,string_ids"}};

// Create Asana client using PAT and deprecation headers.
const client = asana.Client.create(deprecationHeaders).useAccessToken(accessToken);

// Request delay in ms
const delay = 200;

Use the search API to fetch unassigned tasks from the design requests project. Note that the search API doesn’t return paginated results so you need to pass the max limit which is 100. If there are often more than 100 unassigned tasks, you can add a function to keep running the script until there are no more unassigned tasks.

function getUnassignedTasks() {
  let workspaceId = config.workspaceId;
  let params = {
    "projects.any" : config.designRequestProjectId,
    "assignee.any" : "null",
    "resource_subtype": "default_task",
    "fields": "gid",
    "limit": 100
  };
  client.tasks.searchInWorkspace(workspaceId, params).then(tasks => {
    randomAssigner(tasks.data);
  });
}

We’ll need a few helper functions. One to shuffle an array and another to assign tasks.

function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
      let j = Math.floor(Math.random() * (i + 1));
      let temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }
  return array;
}

function assignTask(taskStringId, assigneeStringId) {
  client.tasks.update(taskStringId, {assignee: assigneeStringId})
}

Our final function will take the array of unassigned tasks and round-robin assign them to the group of shuffled designers from the config file.

We will use an interval to loop so we can control the speed of the requests. You can change the delay with the const you declared earlier. This is a balance between speed and staying within our concurrent request limit. In node, a normal loop would send all requests at once, which doesn’t work in larger projects.

function randomAssigner(unassignedTasks) {
  let shuffledDesigners = shuffleArray(config.designers);
  let numDesigners = shuffledDesigners.length;

  // Let's use an interval to loop, so we control how quickly requests are sent
  let index = 0;
  let interval = setInterval(function() {
    assignTask(unassignedTasks[index].gid, shuffledDesigners[index % numDesigners]);
    index++;

    // When our index reaches the end, we're done
    if (index >= unassignedTasks.length) {
      clearInterval(interval);

      console.log("You've assigned " + unassignedTasks.length + " new design requests");
    }
  }, delay);
}

Then we just need to call our getUnassignedTasks function to kick-off the script:

getUnassignedTask();

Now run your script, sit back, and watch the bot do your work.

$ node app.js

Congratulations! Now go above and beyond

You’ve created an Asana triage bot. Let’s explore a few ideas on how to make it even better.

Deploy your app

While you can run your bot from the command line, that seems like a lot of work to run a bot that’s supposed to eliminate work about work.

One option is to use launchd to automatically execute your script (launchd is like cron but better). Here’s a tutorial to get you started with launchd.

The next step would be to deploy to a hosted server. Here’s a guide exploring some of the popular hosting providers. Hosting your app makes it more resilient and allows you to create more sophisticated apps (e.g. use webhooks).

Use Asana as your config file

To take your bot’s accessibility to the next level, put your configuration in an Asana task(s) and then have your script read that task(s) for instructions. This allows you (or anyone else) to make config changes without touching any code. For example, if a designer is on vacation, you can easily remove them from the group that gets assigned requests. Similarly, if a designer joins or leaves the team, this change could be easily configured in a task instead of having to change the bot’s code.

To see this approach in the wild, checkout Ohmega, an automation framework we created. Here’s the configuration service that reads a tree of tasks for its configuration.

Use webhooks for real-time triaging

If you need your bot to react to changes in real time, then you’ll need to use webhooks. We built a python webhook inspector to help developers get started using Asana webhooks.