Programming

20 years of Blaze...

The 1st of September marks a major milestone in my life.  It will mean that I have been running my company, Blaze Business Software Pty Ltd for 20 years now.  Two decades.  It seems almost unbelievable to me at times.

Back in September 1996, I had only been married for a month, I was about to turn 30, and I decided to start a software consultancy business out of my bedroom.  Thus began the rollercoaster, including getting an office in the Cullen Bay area of Darwin, growing the team to at one stage around 16 people, and then now coming full circle to just my wife and I working from a home office again in a 'lifestyle' business.

So many changes in the IT industry at that time.  When I started Blaze, the internet was just hitting mainstream here in Australia, and everything was still dial up.  We were one of the first offices to get an ISDN line into our office, and I clearly remember setting up a small Windows 98 server in the back which was running some sort of DOS mail daemon so that we could have individual email addresses for every employee.  Something that was so rare back then.

We were also one of the first companies locally to upgrade to Microsoft Exchange and implement ActiveSync.  I clearly remember proudly showing off how I could read and reply to emails on my Palm Pilot in real time to all my clients.  Nowadays that is just an expected thing, but back then I was pleased that we were pushing the envelope and being cutting edge.

Lots of nice memories, such as being the finalist in the Telstra Small Business Awards up here in 1998 I think.  Lots of other small awards and achievements.  But there were also some really tough times, and many days where I didn't know whether I wanted to close the doors forever and go raise sheep in the Italian mountains.

But through all that, I still wake up every day and look forward to doing the work I do.  I am always grateful to have met so many wonderful people through my business.  From clients (many of whom I still work with 20+ years later), to employees who have become close friends, to colleagues and competitors and everyone who has walked through the doors or called in the past 2 decades.  Thank You.

Proving that it is never too late to be a 'startup', this year I have embarked on a whole new reboot of the business, as we become a SaaS company providing subscription based business software.  Given that I will be turning 50 this year, I don't know if I will have the energy to keep on with the consulting and support role for many more years, and I am looking forward to setting up a passive income source from a modern, web based subscription platform.

Just another step in our long and interesting journey.  Hope to see you all along the way...

One month with the Apple Watch

While I am an Apple fan, and got a lot of their hardware devices, I was never really enamoured by the Apple Watch when they announced it, and never really planned to get one.

However, about 6 weeks ago, I got a special promotional offer from one of my credit cards companies for an Apple Watch Sport edition which was essentially free, because of the points activity on my business card.

So I decided to take them up on it and place an order.  It arrived within a week and I have been using it every day since then.  Bear in mind that I haven't worn any sort of watch for nearly 20 years now, it took some getting used to, having this metal weight on my wrist again.

Here are my observations on the device.

The Good

I must say that I have been pleasantly surprised at how comfortable, and how quickly I became used to wearing a watch again.  This is the smaller Sport watch edition, and I think the size and understated band suits my skinny wrist better.

I like the fact that I can customise the watch face, and I love that pairing it up with my iPhone 6 automatically loaded up all the Watch compatible apps on the device immediately.

Things I have especially loved is using Siri on the watch - far easier that the phone, especially when I have been cooking and I can just raise my wrist to my mouth as ask Siri to set timers etc.  Same as when I have been working in the garden, although in that situation, there have been a couple of cases where the reminder settings (to move the sprinklers) didn't take because the actual phone was in a distant room inside the house.  

The need to pair the watch to the phone to do simple tasks is one thing that needs to be revisited I believe.  A little more independence for quick tasks would be handy.

I have also started using the Gyroscope app in the past few months, and I like that the watch will integrate with Apple HealthKit, which then integrated with Gyroscope.  Love that my heartbeat stats and steps taken are recorded and consolidated in one place.

However, there have been a few issues because I think I have interconnected too many apps!  I linked RunKeeper with Gyroscope before I got the watch, and now I think the combination of RunKeeper and HealthKit trying to update my steps concurrently is causing some issues with inaccurate data.  I will have to try and unlink some of the apps to simplify.

The Bad

The biggest issue I have with the watch is the battery life.  I have to charge it up at least once a day, and it takes around 90 minutes to charge up fully on the magnetic charger that Apple provided.  The watch always seems to go flat at inopportune times for me, so I end up doing things where I needed the watch (e.g. exercising) without it.

I also wanted to get the watch to replace my old FitBit and Misfit fitness tracking devices - mainly for sleep tracking.  Both the MisFit and FitBit could automatically track whenever I was asleep and generate stats for my movement and rest periods, but the Apple Watch required an app for that.

I ended up purchasing "Sleep Pulse" and I bitterly regret doing so.  I find it quite useless for tracking sleep as you have to manually activate and deactivate it when going to bed.  The interface is also really clunky and there are LONG delays between tapping on the screen and getting feedback.  It never works properly and I am missing reports and stats almost every second day.

Another thing that I could not believe wasn't a built in feature on the watch, is the ability to select podcasts on the device.  When I am walking in the morning, I always listen to a selection of podcasts, and I would dearly love to be able to select from my list of subscribed casts.  Sure I can start/stop and wind back/forwards once one is playing, but I cannot get a list of my subscriptions on the watch.

I do love the way I can just raise the watch and say "Hey Siri, next podcast" to skip through them, but so far have had no luck trying to specify them by name, e.g. "Hey Siri, play me the podcast by Tim Ferris".

 

Overall, I am quite happy with the watch, although I still wouldn't actually pay money for one at this stage.  

I remember when I got the iPhone and iPad, I was excited about developing apps for the device and seeing how I could push the boundaries of my coding skills.  The Apple Watch unfortunately hasn't given me that feeling.  I feel no compulsion to try and write any apps for it to see how I can improve my life.

It will be interesting to see how Apple develop this device in the future, or whether they will simply abandon it, but in either case, it has been a fun experiment to try it out, and I will likely keep wearing the one I have until it finally breaks.

 

Building a Status page for $5 per month

When we first built HR Partner, I wanted to have some sort of status page like most web apps do, to let our customers know about uptime availability and any scheduled maintenance that we had planned.

Our HR Partner status page at: status.hrpartner.io

Our HR Partner status page at: status.hrpartner.io

Looking at most of the commercially available offerings, I found that while excellent, they were quite expensive, when compared to the project management, accounting and bug tracking tools that we already subscribed to.  Being a relatively small, boot strapped startup, I didn't want to add to our already high monthly subscription burden too much at this stage.

Eventually, my search led me to Cachet, which is an open sourced version of a status page app, that seemed to have most of the features that the 'big boys' did.  End of the day, we managed to host Cachet on a virtual server for around $5 a month, and given that the cheapest commercial variant we found was $29 per month, I am happy that we got something working for a budget price that is hard to beat.

Given the buyout of one of the main commercial vendors StatusPage.io by Atlassian today, a lot of people have seen me post about my efforts and have emailed or PMd me to ask how we went about this, so this post will hopefully let you know the steps we took.

Hosting

Our main HR Partner web app is hosted by Amazon AWS, in their us-east-1 region.  Because we wanted some sort of redundancy in case of a major Amazon outage or regional catastrophe, we decided to host our status page on a Digital Ocean Droplet over on the West coast.  Different providers, different infrastructure, different areas.

So the first thing we did was to set up a VPS in Digital Ocean.  I picked the cheapest droplet they had, which was a $5 per month server running Ubuntu 14.04 (64 bit) with 512MB of RAM and 20GB of storage.  Cachet doesn't take much in the way of resources at all, so this was plenty for us.

The Stack

Once the Droplet was up and running, we just opened up a console to the server from within our DO control panel, and installed MySQL on it.  Digital Ocean have a great article on how to do this right here.  We simply followed the instructions step by step.

Next step was to follow the equally great instructions from the Cachet documentation right here to install Cachet on that VPS.

I believe the only tricky thing that we had to do was tweak the permissions within the Cachet folder.  I believe we had to chown the folder and all subfolders to the www-data user and group.

Configuring Cachet

Once we had Cachet installed as per above, we adjusted the .env file to use our preinstalled MySQL instance for the database, and also to use our normal Amazon SES service for the sending of emails.  I believe we had to also change the default queue driver for sending emails.  Here is what our config file looked like:

APP_ENV=production
APP_DEBUG=false
APP_URL=http://status.hrpartner.io
APP_KEY=***secret key here***

DB_DRIVER=mysql
DB_HOST=localhost
DB_DATABASE=cachet
DB_USERNAME=***yourdbusername***
DB_PASSWORD=***yourdbpassword***
DB_PORT=null

CACHE_DRIVER=apc
SESSION_DRIVER=APC
QUEUE_DRIVER=sync
CACHET_EMOJI=false

MAIL_DRIVER=smtp
MAIL_HOST=email-smtp.us-east-1.amazonaws.com
MAIL_PORT=25
MAIL_USERNAME=***yourSESuserIAM***
MAIL_PASSWORD=***yourSESkey***
MAIL_ADDRESS=status@hrpartner.io
MAIL_NAME="HR Partner Status"
MAIL_ENCRYPTION=tls

That was really about it!  (Oh, don't forget to let Amazon SES know about the email address that Cachet will be using to send emails as - in our case status@hrpartner.io.  Otherwise it won't pass the SES spam filtering).

Last thing was to tweak our Amazon Route 53 service to point status.hrpartner.io to our Digital Ocean VPS IP address.  Done!

Now it was all a matter of setting up Cachet with our components and needed to be reported on, and we were away.  All in all, I think the install and configuration took less than an hour to do.

BONUS: Auto update

Because HR Partner is a fairly complex app, with multiple sub apps for the API, reporting engine etc., deployment can take a while to do, and can result in slow performance for up to 15 minutes at a time while the virtual instances are updated and synchronised.

We use Amazon's Elastic Beanstalk command line tools to deploy changes, and at first our procedures meant that before we ran a deployment, we manually logged into our Cachet server to flag the services that would be down, then deployed, waited, and went back to Cachet to flag them 'green' again.

This was quite tedious, and I wondered if there was an automated way.  It turns out there is.  Cachet has a great JSON API, so what we did in our projects was to create a couple of files under the .ebextensions folder in our project folder.  These files contain the scripts that we wanted Elastic Beanstalk to run before and after deployment.  First, we created a file called 01_file.yml for the before script:

files:
"/opt/elasticbeanstalk/hooks/appdeploy/pre/02_cachetupdatestart.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/2
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/4
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/5
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/6
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/8

Then we created a 02_file.yml for the after script:

files:
"/opt/elasticbeanstalk/hooks/appdeploy/post/02_cachetupdatefinish.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/2
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/4
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/5
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/6
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/8
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X POST -d '{"value":1}' http://status.hrpartner.io/api/v1/metrics/1/points

(Replace the [secret token] above with your unique Cachet API token.)

Now whenever we do an eb deploy command, the relevant status page components are marked 'yellow' for the duration of the deployment, then brought back up to 'green' again when completed.

Cheap As Chips

The only running cost for our status page is the $5 per month for the Digital Ocean hosting.  That is all.  We've been running this configuration for some months now with good results.  When revenue and usage gets to the point where we need to update this, then we may look at some of the commercial offerings, but for now, this setup works well for us.

I hope we have managed to inspire others to try the same.  As always, if you have feedback or suggestion on how we can do this better, I would love to hear from you.

 

 

 

 

Picking Non Random Colours for the UI

I think we are up to Part 9 of our often interrupted feature posts on the building of our new human resources SaaS app HR Partner.  I've lost count a little bit, but today I wanted to talk about one of the design issues we came across when creating the dashboard.

We love using the little pie charts from chartJS to show the relative breakdowns of male/female employees, or distribution across departments and employment statuses.  The issue was, we didn't know how to best create a colour palette for the pie segments.  You see, our users can have anything from one to many dozens of pie slices, depending on their organisation and operating requirements.

For this reason, we didn't want to create a set number of colours in our palette, mainly in case our customers exceeded this limit.  We also didn't want to generate totally random segment colours each time the chart was generated because I believe that a part of a good UX is consistency, i.e. if a customer is used to seeing light blue for the department 'Finance', then seeing it as a dark red next time can throw them off.

Additionally, one of the big features of HR Partner is that HR consultants may work across completely different company entities on a day to day basis, and it would be nice if the Finance department in one company dashboard was the same colour as the Finance department in a totally separate company.

For that reason, we decided to set the segment colours based on the segment names.  So the name 'Finance' would generate the same colour on ANY company.

Our first efforts at this resulted in some quite garish colour choices which was not pleasing at all, so in the end I decided that we would try and restrict the colours to lighter pastel hues that wouldn't clash too much, but still be fairly easy to discern.

Secondly, I also realised that because our algorithm was only taking the first 6 characters of the name, there could be collisions with similar department or employment statuses (e.g. 'Part time' and 'Part time permanent' would result in the same colour).  I also wanted similar sounding names (like 'Finance' and 'Final') to generate colours that were not too similar to each other, so I decided to do a simple MD5 hash on the name to generate a semi unique hash upon which to generate the colour.  

Here is the Ruby helper method that we use to create the colour for the view.  It simply takes a text seed string, and generates a CSS hexadecimal code for the colour.

def get_pastel_colour(seed)
  # Generate a pleasing pastel colour from a fixed string seed
  colrstr = Digest::MD5.hexdigest(seed)[0..5]
  red = ((colrstr[0..1].to_i(16).to_f / 255) * 127).to_i + 127
  green = ((colrstr[2..3].to_i(16).to_f / 255) * 127).to_i + 127
  blue =((colrstr[4..5].to_i(16).to_f / 255) * 127).to_i + 127
  "#" + "%02X" % red + "%02X" % green + "%02X" % blue
end

 

I think it ended up quite pleasing to the eye. We ended up using the same code to generate the colour within the calendars too, to get consistency with respect to leave categories.

 

I'd love to hear from other developers on how to improve on this so the colours can be a little brighter and stand out from each other a little more.

Disclaimer: Not saying we were the first to ever 'invent' this method, but there wasn't a lot that I could find on Google, so I thought I would post here in the hopes that it might help someone else who needed something similar.  The code above is based on something I found on StackOverflow, but I cannot find it again now to post proper attribution.