How we keep running by running a slackbot.
At Nordcloud, we have a lot of community events and challenges. Polish department has a running challenge this spring — the idea is very simple, everyone who will run 200 kilometers or more during this spring will get a special award. We’ve decided not to force usage of a particular running application — we wanted to avoid Apple Watch users fighting against Polar users, Endomondo against Strava and so on. At first, our HR department idea was to keep some Google Sheet with data gathered from screenshots via Slack, but hello — we are a technology company!
I am an amateur runner, and one of the reasons why I love to run is that during aerobic workouts I get the time to clear my mind — this is when ideas come to me. So during one of my late evening runs I thought — we should create a tool to automate the counting. Next thing that came to me was that if we want to be app agnostic we should use a universal format — GPX. It’s GPS Exchange Format, basically an XML file that contains information about GPS tracking. As it’s a fairly popular almost every sports application can export a training report in this format.
So I came back home and started coding right away — as an interface for uploading those files, I’ve decided to use… Slack with custom App. The app is basically a webhook — the way it works is that every time you mention the bot (in our case it’s called @NCRunningCoach) defined webhook will get triggered. The rule is that every time you do so, you should attach a GPX file to the message. Slack will then call the webhook with a payload containing information about the file.
On the slack side, after creating a Slack App I needed to create an Event Subscription. I only want to get my webhook triggered when someone mentions the app, so the only event that I am subscribed to is app_mention.
For the webhook code, I’ve decided to use the Serverless Framework. I’ve created a very simple Lambda function connected to API Gateway. For storing the data it uses DynamoDB. The idea was to make it as simple as possible, so for each training that will be logged I am just storing the username, the number of kilometers, start and end timestamps and duration. For analyzing GPX files I’ve gone with an open source library Quantified Self Lib. With all pieces in hand, my job was only to make them work together.
The code above is, of course, simplified a bit for the sake of readability, but it gives a basic overview of what our bot needs to do:
1. Get Slack event
2. Download attached file
3. Pass file contents to GPX interpreter and get back the data about a training
4. Save data to DynamoDB
5. Call Slack API to send a notification to the user about kilometers added to the challenge.
The DynamoDB key is combined Slack username and workout start timestamp. This way I ensure that if one training will get submitted a few times by the same user, we will not get multiple DB entries.
This is how it works:
The last point was to create a place to check the current results. I wanted to do that as quick as possible. So I did — I created an AppSync API with DynamoDB as a data source. Then created React app, connected it to AppSync using AWS Amplify and published to S3 using AWS Amplify CLI. Amplify is amazing for such jobs — creating and deploying the leaderboard took like one hour. The code is fairly simple — it just gets all the data from DynamoDB and then creates a list with runners and their kilometers submitted to the date.
The challenge is on. We’ve already made almost 2000 km combined. Some of us are running their first kilometers ever, some are just logging their regular training, and I believe that this interactive way of submitting our workouts is very energizing — adds a little bit of healthy competition, I had a few “oh no he did his training, I should go and do mine too” moments already. And it saves a lot of our HR friends time ;)
At Nordcloud we are always looking for talented people. If you enjoy reading this post and would like to work with public cloud projects on a daily basis — check out our open positions here.