Dynamic Badges with Shields.io and Runkit

Taylor Nodell
4 min readJul 28, 2019

One day I woke up and wanted to be a good developer, by writing tests for my flower game. Unfortunately, writing tests for components you’ve already made can be a little boring. Switching into test driven development can be a little more difficult than starting out with TDD.

So I needed some motivation. I had seen badges on other GitHub projects that display whether the build is passing, the license, dependencies and all kinds of fun stuff. But what I really wanted was a badge that would display my code coverage and update as I added my tests.

Shields.io Github page with many badges

Shields.io looks to be the service for that! They have many easy APIs that hook right into common Continuous Integration services like TravisCI and CircleCI. You can even create your own custom badges! Creating a static one is relatively straight forward. But for me, because I’m not using a supported CI service (I’m using Buddy, and still learning), I needed to create a dynamic custom badge.

By providing a JSON endpoint to Shields, you can populate the badge with whatever you what, changing based on any parameters you pass in. But I really didn’t feel like setting up a server that would grab my code coverage data then constantly serve that little bit of information just for a badge. Great that they link to Runkit right there on the page then.

Runkit’s instant API feature described on their home page.
Runkit’s instant API feature

Runkit lets you program JavaScript in the browser! What will they think of next!!!

Jokes aside, Runkit lets you program node JavaScript in the browser. The implications here are far greater than just being able to populate badges from a JSON endpoint. But heck, that’s what I’m here to do, so I’m going to do that.

You can play around right in the browser with Runkit’s Endpoint service. Below is all the code I need to get a url to pop into Shields.io.

First I require request and requestPromise as I’ll be making a request to my github data, which has my coverage. I’m running Jest as my testing engine and adding the flag --coverage creates a nice reporting file. I grab the url of that raw file from github and store it as a variable. I’m expecting a query to be present and one of the four types. Note: I had to bump up the default version of node, to use the Array.includes method.

const endpoint = require("@runkit/runkit/json-endpoint/1.0.0")
const request = require("request") // peer dependency
const requestPromise = require("request-promise")
const queryTypes = ["lines","functions","statements","branches"]
const url = "https://raw.githubusercontent.com/nodes777/flower-game-phaser3/master/coverage/coverage-summary.json"
endpoint(module.exports, async function(req)
{
try {
var unparsed = await requestPromise(url)
var json = JSON.parse(unparsed)
} catch(e) {
return {error: "Runkit could not retrieve page"}
}

const queryType = req.query.type
const formattedLabel = queryType + " coverage"

if (queryTypes.includes(queryType)){
const percentage = json.total[queryType].pct+"%"
return {
"schemaVersion": 1,
"label": formattedLabel,
"message": percentage ,
"color": "orange",
}
} else {
return {
"schemaVersion": 1,
"label": "You need a valid queryType",
"message": queryType ,
"color": "red",
}
}
})

Then all I do is request the url, parse the data as JSON when it arrives (I always forget to do this), and determine if a valid queryType was passed in the Shields.io url (the url that will be calling this script). I format the data the way I want it to display and return an object with the same shape that Shields.io is expecting. If you don’t follow the schema, you might get an error from Shields saying something like “inaccessible” or “resource not found”, so make sure to return an object with these properties:

{   
"schemaVersion": 1,
"label": "A label!",
"message": "Whatever text you want to render, for me, this is my percent coverage",
"color": "orange"
}

Once you’ve got your Runkit Script going, it’s live! It’s also public, so you don’t want to put any private data through this. You can click the endpoint button and get your very own super easy API! Then you can pop that url into the Shields.io endpoint page and get your dynamic badge. Finally grab the url that Shields.io returns, and use that as your image url in your README.md

The Shields.io endpoint page with the url from Runkit as the url to generate the badge
The updated endpoint page with url to generate the badge

The one thing I’m not understanding is why the schema requires a label but then doesn’t use that label as the alt tag for the img. This creates an accessibility issue. I can provide one in the markdown manually, but because the percent coverage is generated dynamically, I have no way to access it and provide it to assistive technology. Below is my markdown for the badge.

![lines coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Funtitled-noopow1jds3m.runkit.sh%2F%3Ftype%3Dlines)

I have opened an issue regarding this, and since this is an open source project, I may even make the PR myself. First though, time to celebrate the original task at hand… my badges are live!

Badges: “code style: prettier” “lines 50.43%”, “statements 50.43%”, “functions 35.51%”, “buddy.works passed”, License: GPL v3
All my badges!

Hopefully this keeps me motivated in creating testable, robust code! And hopefully you’ve learned something along the way too. Thanks for reading!

--

--

Taylor Nodell

Developer. Musician. Naturalist. Traveler. In any order. @tayloredtotaylor