Elixir — The Nags hd

Dan Bowett
4 min readJul 5, 2016

--

I started playing with Elixir around a month ago. Having come from a non-functional background (PHP and Python) I found it engaging almost immediately. The syntax was familiar but once I dug a little deeper I realised that’s where the similarity ends. I read tutorials and books but knew I’d not really learn anything until I could put it to use in a real word problem. Luckily one presented itself.

The Problem

At work we use a SaaS product called Harvest (https://www.getharvest.com/). It’s used for tracking time spent on projects and it’s great. The problem is remembering to start and stop timers throughout the day. Quite often users would realise at the end of the week that they had forgotten and then have to backfill all their time.

The Idea

Using Elixir, write a nagging script that checks each users time-sheets throughout the day. If insufficient hours have been logged then email them to politely remind them. Harvest has a well documented API and I could see all the information I needed was available.

Strategy

Try as much as possible to use a functional style to achieve what I needed. I kept two main concepts in mind:

  • Write small composable functions which can be chained together.
  • Think in terms of transforming data from one form to the next.

API Access

First I had to find a client library in Elixir. I found HTTPotion. I wanted the script to be as maintenance free as possible so the first thing I needed was a function to fetch a list of active users from the API.

The get_auth function here just takes my Harvest API username and password and Base64 encodes them to include in the Authorization header. The get_users function returned a list of users like this:

I was only interested in active users so I wrote another function to filter the list.

Now I had a list of active users which I could loop through and fetch their current timesheet for that day.

The function returned a List with two items:

  • The users email address - I needed this in tact to send the reminder later.
  • The full timesheet response for that user.

The timesheet response:

I needed to sum all the ‘hours’ values in the list of ‘day_entries’.

sum_hours() was my first attempt at recursion. At first it seems odd but you soon realise the power vs. a for loop in PHP where you have to maintain a mutable variable.

group_hours() returns a list containing the users email and the total number of hours they’ve recorded that day.

The Pipe Operator

Now I had all these functions built correctly I could combine them using Elixir’s pipe operator into one easy to call function:

I love how readable the code above is. It’s clear to anyone what is happening and that must make it more maintainable. This returned a simple list like this:

All that was then left to do was to see if each user had logged the expected amount of hours and email them if they hadn’t.

Calculating ‘expected’ hours

Luckily all our users start at 9am so it was quite easy to work out how many hours they should have done. I fetched the current hour of the day and subtracted 9. I then further reduced this number by 1 to work out my expected hours figure. For example. By 1pm I expected them to have logged 3 Hours (13–9–1).

The hours_since_nine function get’s the hour using pattern matching on the Erlang calendar local_time() function. Now I knew how many hours I expected I could further filter my list of hours for active users.

Email Alerts

Once I had this final list it was a simple case of emailing each person to ask them to check their timesheet.

Here I used the Enum map function to apply the actualSend function to each person in the list. I’ve not included all the detail on how the actualSend function works here but I used the Mailman module which made it really simple.

Conclusion

This small script is now in production and nagging users on a regular basis. I used escript to create an executable shell script I could call via cron. It runs mid morning and late afternoon every work day. I’m yet to tell if it’s effective!

I’m really pleased with my first foray into Elixir and will definitely be looking for other projects I can use it on. I’m sure there are nicer/better ways to do what I’ve done here so I’d love to hear some feedback.

--

--

Dan Bowett

Depending on the day I'm a different mix of developer, sys ops guy, database admin, lego builder, dad, husband.