Triage bot
Introduction
Why build a bot?
When processes get complex in Asana, there can begin to be "work about work." This could be happening to you 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 among users who work with support inboxes or request projects.
Open source example
If you want to skip ahead and see the code for the triage bot, you can visit the project within the devrel-examples repo.
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 who 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:
Before you begin
Create your bot user in Asana
Create a new Asana account for your bot (instructions for inviting users). You will want to create a separate, 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 other users in Asana who may encounter it. Note that if your bot is a guest member in Asana, 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 the developer console directly here.
Next, click + Create new token and follow the instructions to get your token. Treat this token like a username and password. Do not share it with anyone and never publish it to a public repository. You may also wish to save the token as an environment variable (instructions on how to do this on Mac). For this guide, the personal access token was saved as an environment variable called triage_bot_pat
.
Create a "sandbox" project
Before we start coding, create a project in Asana to use as a sandbox. While not required, you can 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 may also invite a personal email as a guest user.
Choose an Asana client library
The Asana API has official client libraries 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/).
Triage bot tutorial
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
Then, install the Node Asana client library and create config and app files:
$ npm install asana
$ touch config.js app.js
We'll continue by adding GIDs (global identifiers) for the workspace, design request project, and designers that will be assigned requests (note that all GIDs 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/{project_gid}/{task_gid}
). 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 JavaScript client library and the config file:
let asana = require('asana');
let config = require('./config');
After that, 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;
Next, we'll 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 also 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})
}
Here, 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 rate limits. 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 run the script:
getUnassignedTask();
Now run your script, sit back, and watch the bot do your work.
$ node app.js
Next steps
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.
Updated almost 2 years ago