[go: up one dir, main page]

Change your colour scheme

Getting my top posts from Umami

Published:

I recently started using Umami for website analytics, and figured I could use the API to output some stats about my blog. Among them was a list of my most popular posts, which I’ve not had any information about before[1].

Accessing the API

Firstly, I need to be able to actually access the API. The way this is done is by sending a POST request to the /api/auth/login endpoint with a username and password in the request body, which returns an auth token. This was pretty straightforward, I just did the call in Restfox, but here’s the equivalent cURL:

curl --request POST \
    --url https://umami.lewisdale.dev/api/auth/login \
    --header 'content-type: application/json' \
    --data '{
    "username": "<your username>",
    "password": "<your password>"
}'

This returns the token, as well as a bit of information about the user. The docs don’t specify how long the token is valid for, but I originally generated mine in the middle of June before getting distracted for a month, so I’m guessing they’re fairly long-lived.

Getting the data

This is just a straightforward GET request to the /api/websites/<site id>/metrics endpoint, where <site id> is the ID of the website you want to get data for. You also need to provide startTime and endTime timestamps as query parameters. Because I want to match the results to my blog posts, I’m using the url type, and filtering the results to only include URLs that start with /post/, but you can choose one of the many other types too, e.g. referrer[2], browser, device, etc.

Here’s my Eleventy data file, which uses Eleventy Fetch to make the request and cache the result for an hour, for no other reason than I didn’t want it to slow down my builds all the time. I just needed to make sure that I set removeUrlQueryParams to true, because otherwise I’m caching the entire URL which includes the current timestamp, which is a bit pointless:

// src/_data/postMetrics.js

const EleventyFetch = require("@11ty/eleventy-fetch");

const siteId = ".."; // The site ID from Umami
const umamiUrl = "https://umami.lewisdale.dev";
const apiKey = process.env.UMAMI_API_KEY;

module.exports = async function(arg) {
    const url = new URL(`${umamiUrl}/api/websites/${siteId}/metrics`);
    url.searchParams.append("startAt", 0);
    url.searchParams.append("endAt", Date.now());
    url.searchParams.append("type", "url");

    const metrics = await EleventyFetch(url.toString(), {
        duration: '1h',
        type: 'json',
        removeUrlQueryParams: true,
        fetchOptions: {
            headers: {
                "Authorization": `Bearer ${apiKey}`,
                "Accept": "application/json"
            }
        }
    })

    return metrics.filter(metric => metric.x.startsWith("/post/"))
        .map(({ x, y }) => ({ post: x, count: y }));
}

The API was easy enough to use, so this worked more-or-less out of the box, thankfully. As a bonus, the data is already in descending order, so I didn’t even need to sort it.

Displaying the data

This was slightly more convoluted. In Eleventy, data files can’t access the collections API as they sit higher up in the data cascade, and likewise there’s no way to access the data object from a config function. Instead, I created a new filter that takes both the metrics and the posts, and just maps the two together:

// config/filters/getPost.js

module.exports = function(eleventyConfig) {
    eleventyConfig.addFilter('metricsToPosts', function(metrics, posts) {
        return metrics.map(metric => posts.find(post => post.url === metric.post));
    })
};

And then I can use it in my template:

{% set popularPosts = postMetrics | take(3) | metricsToPosts(collections.posts) %}

<section class="stack-md">
    <h2>Popular posts</h2>
    <ul class="stack-2xs" role='list'>
    {% for post in popularPosts %}
    <li><a href="{{ post.url }}">{{ post.data.title | safe }}</a> <time datetime="{{ post.date | dateToRfc3339 }}">{{ post.date | dateDisplay }}</time></li>
    {% endfor %}
    </ul>
</section>

And that’s it! The list of top posts is fairly static right now - I’ve only been running Umami for about a month and I’ve had one fairly popular posts, and then a couple of normal low-traffic posts. I imagine that the top post will be there for a long time - or I’ll shorten the timespan to the last month so that it’s more fluid[3].


  1. Well, I’ve got server logs but there’s so much cruft in there that sifting through it is a pain ↩︎

  2. Or referer, refferrer, rrefferrerr or however it’s misspelled everywhere ↩︎

  3. Until I have another month where I don’t write and this section winds up blank ↩︎

Tags:

About the author

My face

I'm Lewis Dale, a software engineer and web developer based in the UK. I write about writing software, silly projects, and cycling. A lot of cycling. Too much, maybe.