Lowering the Barrier

Making programming (everything) more accessible

23 Sep 2020

Append and check

Append and check

At Repl.it we put as many tools as we can into slash commands on Slack. It makes it really easy for everyone to see what tools exist and how to use them, and it turns our #ops channel into a log of what happened to production when. (Whether or not you want a Slack channel to be the source of truth for all time is another question, but having a single source of truth at all for what happens in production is hugely valuable.)

I’ve spent the past few mornings writing a slash command that runs smoketests (using walrus.ai’s straightforward API) on the staging version of our app web app. Slash commands expect a response within three seconds. The tests can take a few minutes to run. One workaround is to give your Slack app permissions to post to channels and have it start and update a thread with its progress. Our deployment app does just that! But I was feeling lazy and didn’t want to muck with app permissions.

Instead, I separated the command that starts the tests (/smoketest start) from the command that checks the status (/smoketest status). The process that starts and runs the tests generates a unique ID for that test run and starts appending data to the repl’s db. Repl.it Database lets you list all keys starting with a prefix, so the process running the tests can add keys starting with walrus-SOME-RANDOM-UUID as data comes from standard out and the function can scan all the keys starting with that prefix to reconstruct the state of the run. It’s not novel to separate viewing data from writing data, but it’s a handy trick when your code has to run asynchronously.

We tend to write our slash commands in JavaScript for a variety of reasons (there are plenty of Slack libraries and examples, it’s super-easy to spin up an Express server, and everyone on the engineering team can read and write JavaScript). Whenever I write async JavaScript, I am guaranteed to spend at least an hour or so figuring out that I forgot to await a Promise:

const status = /* should be an await here! */ checkTheStatusInAnAsyncFunction();

I’m writing this down so the next time I wonder why a function doesn’t appear to be called or there’s no key in an object even though I clearly put it there, I’ll check the async function calls. As I write this it occurs to me that a linter might be able to help here.