Skip to content


FUME – A Better Confluence User Macro Editor

  • by

One of the best and most overlooked aspects of Confluence, both by Atlassian and Confluence administrators, is user macros. There are so many useful scenarios for user macros. Here are some:

  • Templated snippets
  • Overriding built in macros example with task list report
  • Quickly creating your own macros
  • Inserting arbitrary html/css/javascript into a page without having to enabled the html macro

However, there are some big usability issues with the user macro editor. First it’s super easy to accidentally delete one. The delete link is right next to the edit link and seriously, there is no confirmation on the delete link. It’s just gone. Ack!

Second, the link to create new user macros is at the bottom of the page. If you have more that what can fit on a screen you have to scroll down to get to the link to create a new one … this just gets worse over time as you create more.

Third, the template box in the editor is just a plain old text area … no line numbers, not syntax highlighting, it’s not even a mono-spaced font! Grr!

Fourth, the cancel button doesn’t ask you to confirm canceling the edit if you have made changes to the user macro and since it sits right next to the save button it’s easy to miss. Hope you can recreate your work quickly.

Finally, every time you save it kicks you back to the list page. So, if you want to make some changes and try it out on a page you have to click back into the editor every time you save and whoops you accidentally just clicked delete instead of edit! There goes all that work.

So, without further ado … FUME. Fantastic user macro editor. The fantastic part is really just because I needed a word that ended in “ume” and that was the only word I could think of. Really it’s not all that fantastic … maybe just great, but gume isn’t even a word. Then I thought “How about  great looking user macro editor”, but that would be glume and … well … yeah, that kinda defeats the purpose. So, FUME it is. All in all I think it is a much better editing experience than the default setup. Here are some of the features:

  • Copy that “Create a User Macro” link to the top of the list page … no more scrolling
  • Delete confirmation on the list page
  • Template box changed to a source code editor with (Ace editor):
    • monospaced font
    • line numbers
    • syntax highlighting
    • find and replace
    • code folding
    • column select
  • Confirmation on cancelling edits of the user macro if the template has been changed
  • Asynchronous user macro saves
  • It will do your dishes and laundry … ok, not quite yet

Update 4/9/2018:

Ignore the “How to Setup” section below. I’ll leave it there, however, for the sake of continuity. I decided to package this up as an add-on in the Atlassian Marketplace. I named it Enhanced User Macro Editor (EUME … pronounced you-me … it’s a stretch I know). It seemed a bit more humble of a name and is more descriptive of what it is. I hope it is as useful for you as it has been for me. Marketplace link below.

Enhanced User Macro Editor

How to Setup

  • Download these CSS and Javascript files. (right click the links and choose “Save link as”)
  • Place them on a web server where they will be web accessible to your user macro editors.
  • Add this to the end of Confluence Admin -> Custom HTML -> At end of the BODY
* Fantastic User Macro Editor           * *****************************************
<link rel="stylesheet" type="text/css" href="http(s)://{your server}/path/to/fume.css">
<script src="http(s)://{your server}/path/to/fume.js" type="text/javascript"></script>
  • Enjoy editing your user macros.  🙂


User Macro List

User Macro List Page

User Macro Template Editor

User Macro Template Editor
Splunk HL7

Parsing HL7 with Splunk

  • by

At my job I do a fair amount of work with HL7. If you work in the medical field you probably know that HL7 is the language that medical systems use to talk with each other. It’s a fairly simple format that uses carriage returns and pipes to delimit fields … ok there are a few other delimiters as well, but the carriage returns and pipes are the big ones. Below is an example HL7 message.

PID|||56782445^^^UAReg^PI||KLEINSAMPLE^BARRY^Q^JR||19620910|M||2028-9^^HL70005^RA99113^^XYZ|260 GOODWIN CREST DRIVE^^BIRMINGHAM^AL^35209^^M~NICKELL’S PICKLES^10000 W 100TH AVE^BIRMINGHAM^AL^35200^^O|||||||0105I30001^^^99DEF^AN
OBX|1|NM|^Body Height||1.80|m^Meter^ISO+|||||F
OBX|2|NM|^Body Weight||79|kg^Kilogram^ISO+|||||F

Each line in the message is called a segment and each segment can be divided into fields based on the pipes. For instance the third line is the PID segment which has patient information such as the MRN (PID 3), patient name (PID 5), birth date (PID 7), etc. The PV1 segment has information that relates to the patient visit. It is a fairly concise format without much overhead and as such is perfect for medical institutions where these kinds of messages are flowing constantly throughout the day.

The Problem

In a typical medical environment there will be a system called the HL7 routing engine that serves as an intermediary between all the various medical systems in the clinic or hospital. The HL7 engine can route messages to one or various systems and transforms them en-route based on rules. Most HL7 engines have the ability to log the messages sent through them in some format.

Often times there may be a need to lookup what messages were sent to various system to troubleshoot problems. In many cases there is no great means of searching through the thousands or even hundreds of thousands of messages sent each day to troubleshoot these issues.

The Solution

About a year ago I was approached by some folks from Splunk about creating a Technical Add-on (TA) for Splunk for parsing HL7. After many months of working with one of their engineers named Joe Welsh we were able to release the free ‘HL7 Add-On for Splunk“. We tested the add-on by throwing millions of our HL7 messages at it to make sure it parsed the messages correctly.

With this TA we can have Splunk monitor our HL7 logs and in real-time are able to quickly search those logs to troubleshoot issues, report on failed messages, and view dashboards to monitor the health of our HL7 environment. I have been super pleased with the results.

If you are a medical institution that uses Splunk check out the add-on … it’s free. See what awesome things you can do with Splunk and HL7. Let me know in the comments if you have found it useful.

Splunkbase Link

Powered by WordPress, running on raspberries

  • by

The Issue

My Raspbery Pi running WordPressMeet my new web server! Isn’t it cute? Up till recently I have been running my website on my home machine. This has worked fine as this site doesn’t get a ton of traffic and my home machine is fairly beefy. However, I’ve always been slightly leery about running my web server on my main computer just for security reasons. If it were to get hacked I could be potentially opening up everything to the hackers. Not only that, in order for the site to be up I had to leave my home machine running 24/7. This is not great on the electricity bill. Also, during the summer months in our not so air-conditioned house it actually adds significantly to the heat. Which is not too fun for my wife who has to been home all day in the heat while I sit at work in the air-conditioning.

The Solution

So, I started looking around for a not too expensive solution to this. As I researched I started seeing more and more people running WordPress on a Raspberry Pi. Now, I can muck my way around Linux, but it’s certainly not my fortĂ©. Fortunately, I found a great tutorial for installing WordPress on a Raspberry Pi that even a Linux noob like me can follow.

It worked great! I was able to get WordPress set up, move my content over and flip the port forwarding over to the Pi within a few hours. I also set up a dynamic DNS client on the PI to keep my address current with my domain registrar. I now have a separate server, that runs on pennies, puts out little to no heat, is completely quiet, and cost very little to purchase. I could not be happier.

Bookmarklet for Creating a CSV From an HTML Table

We have a wiki at work that we use for documentation called Confluence. It has the ability to export pages to Word or PDF, but a lacking feature is the ability to take a table and export it for Excel. So, I decided to create a bookmarklet that will allow you to select an HTML table on any web page and create a CSV file from that table that can be downloaded to your machine. Below is the bookmarklet. Just drag it to your bookmarks toolbar. Then give it a shot by clicking the bookmarklet and the below example table should get a link right before it that says “Export to CSV”. Click that link and you will be prompted to download the CSV version of that table. Let me know if it is useful for you.

On thing of note, this will not work correctly in Internet Explorer 9 and below as IE will not allow data uri’s for anything other than images.

Export to CSV <- drag this to your bookmarks toolbar

05/08/2019 – Enhanced to not kill line breaks.
09/16/2016 – Now works with IE 10+.
04/19/2016 – Fixed issue with tables that have header cells not on top.

Example Table:

Heading 1Heading 2Heading 3Heading 4Heading 5Heading 6
Data 1, 1Data 1, 2Data 1, 3Data 1, 4Data 1, 5Data 1, 6
Data 2, 1Data 2, 2Data 2, 3Data 2, 4Data 2, 5Data 2, 6
Data 3, 1Data 3, 2Data 3, 3Data 3, 4Data 3, 5Data 3, 6
Data 4, 1Data 4, 2Data 4, 3Data 4, 4Data 4, 5Data 4, 6
Data 5, 1Data 5, 2Data 5, 3Data 5, 4Data 5, 5Data 5, 6
Data 6, 1Data 6, 2Data 6, 3Data 6, 4Data 6, 5Data 6, 6

Bookmarklet Source: 

    function getJavaScript(url, success) {
        var script = document.createElement('script');
            script.src = url;
        var head = document.getElementsByTagName('head')[0],
            done = false;
        script.onload = script.onreadystatechange = function(){
            if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
                done = true;
                script.onload = script.onreadystatechange = null;
    function addCSVLinks() {
            jQuery(this).attr('data-csvtable', index).before('<a href="#" class="csvLink" data-forcsvtable="' + index + '">Export to CSV</a>');
            var text = '';
            var csvTableIndex = jQuery(this).attr('data-forcsvtable');
            jQuery('table[data-csvtable="' + csvTableIndex + '"] tr').each(function(){
                jQuery('td, th', this).each(function(index){
                    if(index != 0) {
                        text += ',';
                    text += '"' + formatedText(jQuery(this).html()) + '"';
                text += '\r\n';
            downloadCSVFile('TableExport.csv', 'text/csv', text);
	function formatedText(html) {
		var ret = html;
		//replace line breaks
		ret = ret.replace(/\n/g, ' ');
		//replace tabs
		ret = ret.replace(/\t/g, ' ');
		//replace multiple spaces
		ret = ret.replace(/\s+/g, ' ');
		//Fix html encoded characters
		ret = decodeHtml(ret);
		//Deal with lines breaks and paragraphs
		ret = ret.replace(/<br>/ig, '\n<br>');
		ret = ret.replace(/<br/ig, '\n<br ');
		ret = ret.replace(/<p/ig, '\n<p ');
		//Deal with quotes
		ret = ret.replace(/"/ig, '""');
		//Deal first character being line break
		ret = ret.replace(/^\n/, '');
		//Remove HTML tags
		ret = ret.replace(/(<([^>]+)>)/ig,"");

		return ret;
	function decodeHtml(html) {
		var txt = document.createElement('textarea');
		txt.innerHTML = html;
		return txt.value;
    function downloadCSVFile(filename, mime, text) {
        if (window.navigator.msSaveOrOpenBlob){
            // IE 10+
            var blob = new Blob([decodeURIComponent(encodeURI(text))], {
                type: 'text/csv;charset=utf-8'
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var pom = document.createElement('a');
            pom.setAttribute('href', 'data:' + mime + ';charset=utf-8,' + encodeURIComponent(text));
            pom.setAttribute('download', filename);
    if(typeof jQuery == 'undefined') {
    } else {

Microsoft, the 90’s wants its security policy back!

  • by

Today, I went to change the password on my Microsoft account from a shortish hard to remember and type password to a nice and secure long and easy to type passphrase. With all of the hubbub these days around hacked accounts I want to make sure that my accounts are nice and secure. I just wish Microsoft was on-board with that.

Phrase1I went to my account and put in a pass phrase that seems pretty decent to me. Here is the first message I got. It seems that my nineteen character passphrase is too long. Why is this an issue I ask? Why does Microsoft care if I want to have a long password? If I want to take on the burden of typing those extra characters what is it to them? This is not a limitation with any other Microsoft system I have ever used (personal machine and work machines). I’m sure their own systems are based on their own stack, so this is an artificial limit that they have put in that has no real value except to make my account easier to hack.

Phrase2Ok, since I can only have up to sixteen characters in my password I guess I’ll have to comply. Sixteen characters is still pretty long right? So, I swapped out some words and made a few tweaks only to be presented with this. REALLY!? By the way, the character that they do not like is a space. So, I thought I would see what characters they do allow. They allow A-Z, a-z, 0-9, and every special character printed on your keyboard … just not space. Ugh!! It seems they are going out of their way to discourage pass phrases when it has been shown time, and again, and again, and again that pass phrases are more secure than passwords.

So, in the end, I am forced to bow to their ridiculous security policy that excludes one character and forces me to 16 characters. What do you think?

Confluence Profile Photo Uploader

  • by

Small disclaimer: this works great on my setup. I have not tested it outside of my setup. It should work just fine as I am using the standard API’s that come with Confluence, but it is an open source project and I’m not getting paid for it, so I haven’t done extensive testing in all scenarios.

At the Vancouver Clinic we use a wiki product called Confluence. It is a fantastic product that is super customizable via add-ons, extensions, custom html/js/css, and SOAP and REST APIs.

Unfortunately, Confluence does not have a way for administrators to bulk upload user profile photos into it. For corporate organizations that want to make sure that user photos are uniform and professional this is somewhat cumbersome. As a result Confluence may have user profiles with Mickey Mouse, Wolverine, or worse as the photo … don’t get me wrong Wolverine is pretty cool, just not professional.

Because of this I decided to write a utility that allows an administrator to sweep in photos in a specified folder with a file mask of “%username%.%extension%” into Confluence. The utility will accept .jpg, .jpeg, .tif, .tiff, .png, and .bmp files. Optionally, you can specify a folder to archive the swept-in photos to after they have been uploaded. Confluence profile photos are 48×48 pixels and since most photos are not square the utility will attempt to do face detection and crop the photo to the largest face in the photo. If the photo is already square you can opt to have the utility not perform the face detection. I am personally not smart enough to write face detection algorithms, so I am using EMGU/OpenCV to do the face detection (which is why the compiled version is so freakin big). Hey, I got no problem standing on the shoulders of giants.

For automation purposes there is a command line switch /headless that will do the upload without any GUI using the configured settings. This works great with Windows Task Scheduler.

This is working awesomely for the clinic and fortunately my manager has allowed me to open source the code. So, I have uploaded it to Bitbucket for anyone to use, or make fun of my code. : ) Seriously, have at it if it will help your organization. And by all means let me know if there you run into any bugs along the way.

Confluence Speakeasy Favorite Menu Extension

  • by

So, lately I’ve been doing a lot of customization at work on a product we use called Confluence. It is quite possibly the slickest wiki I have ever used. It has an awesome plugin model and pretty huge market for community developed plugins. One plugin that Atlassian has created allows for creating modular client based extensions to the wiki. It is called Speakeasy.

menuI happen to have a few favorite pages in the wiki that I refer to all the time and I have been wanting a favorite menu that shows up on every page. Alas, I couldn’t find one that existed, so I decided to make my own.

Feel free to download the extension and modify it to your needs.

I have released this on the Atlassian Marketplace for free.

Leap Motion – A Review

  • by

So, I want the Leap Motion to be this awesome thing that makes my computer word kinda like Minority Report. I really want it to be a totally new way of using my machine that doesn’t just replace the keyboard and mouse but can enhance it much the same way that touch interaction has these day. Unfortunately, I just can’t say that right now.

So far it has been kind of a let down … especially the apps that are geared toward native computer interaction. So far the app that Leap has put forward is just way too hard to use. It is super difficult to figure out in 3D space where to put you hand so that you get a cursor on your monitor, and once you have done that actually pointing at something and “clicking” it is super painful. I have to go so slow that it makes it worthless to use. I was hoping that they would give you the ability to calibrate the Leap to the position of your monitor and they in essence allow you to use your monitor as a touch screen … thus making it much more easy to accurately “click” on things and interact with the native OS. Similar to this Youtube video. Alas, they went for the 3D air clicking approach which just doesn’t work.

Granted this is a new device and hopefully it will continue to get better. I do hope that someone decides to create an app that lets you turn ordinary monitors into touch screens … maybe I’ll work on it. icon_smile

Kid Browser

  • by

Desmond always amazes me as to how much he can grasp. Kids are such sponges and they can do way more that we think they can do. For example we let him go to a few websites that we have deemed safe for him to look at. I am amazed that he picks up how the games work without Melissa or I telling him how to do them. He has gotten really good at using the mouse.

wommAnyway, occasionally we would let him look at these sites and then later we would find that he minimized the browser and Quicken or something else that we didn’t want him messing with was up on the computer. So, I decided to make a web browser specifically tailored to his age so that he can only go to the sites that we say are ok and he cannot click out of them into anything else. Also, I didn’t want him to easily be able to minimize the browser or close it. Thus, KidBrowser was born.

I don’t claim that it is any great work of programming, and it could probably be more visually appealing, but I am no graphic designer. Anyway, if you have kids and you want to use it yourself feel free to grab a copy and set it up on your machine. I don’t claim that it will work perfectly on your machine. I have only tested it on my box, which is a Windows 7 machine. However, if you have issues let me know and I will try and fix it.

I have created a bitbucket project for this browser, so feel free to download the source and make fun of my coding abilities if you want to.  Links and screenshots are below.

Bitbucket Project

Latest Compiled Version

This is what the browser looks like. The left navigation is the web pages that we have deemed are ok for him to look at. You can add as many as you like.  If you add more than will appear in the column you will see scroll arrows appear so that you can scroll the nav.


This is the config module.  You can use this to add sites to the navigation menu. Currently, I suggest using the Gecko browser option as IE seems to have an issue with at least one site I that I know of, Disney Junior.  The configuration module isn’t pretty and I will probably work on it a bit more, but I wanted to get something made so that the browser could be easily configured. I think most everything is pretty self explanatory. There are a couple things that I will explain below.


Resolves To:
This is mainly used in conjunction with the “Allow Sub-links” checkbox. Sometimes if you put in a web address it will redirect you to another page. For instance if you put in it might redirect you to If you have allow Sub-links checked and your “Site URL” is set to then you would be able to visit any links that start with, unless specifically blocked in the block section.  However, if the site automatically redirects the home page to something else such as then in order to get all of the pages under and the home page you would need two different entries. That is why implemented the “Resolves To” so that the browser will consider the redirected first page to be apart of the rest of the site.

Allow Sub-links:
This will allow any pages that start with the “Site URL” to be viewed.  Thus is the “Site URL” is, then would be available as well.

Blocked Sites:
Blocked sites take precedent over the Allowed Sites.  By default everything is blocked except what is specifically allowed.  However, if you allow sub links for a certain site there may still be a couple undesirable pages. You can block these pages specifically. Additionally you can block sub pages or any pages that start with a certain URL. Thus, if was allowed with sub-links, you could block the Winnie the Pooh video page by putting in for a blocked url. If you clicked “Block Sub-links” then anything under would also be blocked … such as

Finding the Character Set For a SQL Field

  • by

The other day I was working on a project and I needed to know all of the different types of characters contained in a SQL field so that I could make a regular expression pattern that would not fail. Thus, I decided to write a stored procedure that would loop through a range of characters and test if that character is contained in a specified field in any records. From there I can get a list of all of the used characters and then more intelligently write my regular expression pattern.

This could also be handy if you are trying to cast a field and you keep getting errors because of a character that won’t cast correctly. An example would be trying to cast a varchar or char field into an int and for some reason or another one of the records has a letter in the field. Your cast will fail and it can be tricky to track down where the issue is. With this stored procedure you can quickly see what the offending character is and then query up all the records containing that character.

Example usage:

exec FieldCharacterSet 'dbo', 'mytable, 'myfield', 1, 0, 255

Feel free to use this if you find is useful, and leave a comment letting me know if it worked well for you.

-- =============================================
-- Author:        Davin Studer
-- Create date: 9/26/2010
-- Description:    This stored procedure will let you
-- know all the characters that are contained within
-- a field of a specified table.  This is handy for things
-- such as creating regular expression patterns and figuring
-- out casting issues.
-- =============================================
create procedure [dbo].[FieldCharacterSet]
    @TableSchema nvarchar(255) = '',-- this is the schema for table to look at
    @TableName nvarchar(255) = '',    -- this is the table to look at
    @FieldName nvarchar(255) = '',    -- this is the field that will be scanned
    @RTrim bit = 1,                    -- should the field be right trimmed before finding the character set ... mostly for char field types
    @LowCharRange int = 0,            -- character scan start point
    @HighCharRange int = 255        -- character scan end point
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    set nocount on;
        @count bigint = 0,
        @sql nvarchar(max) = '',
        @selects nvarchar(max) = '',
        @error varchar(200) = '',
        @field nvarchar(264) = '',
        @sqlbuild nvarchar(max) = ''
    declare @chars table(
        [character] nchar(1)
    declare @statements table(
        [sql] varchar(max)
    if @TableName = ''
        set @error = 'You must supply a table name.'
        raiserror (@error, 15, 1)
    if @FieldName = ''
        set @error = 'You must supply a field name.'
        raiserror (@error, 15, 1)
    if @error = ''
        -- Bracket the table name to avoid naming issues
        if left(@TableName, 1) <> '[' and right(@TableName, 1) <> ']'
            set @TableName = '[' + @TableName + ']'
        -- Bracket the field name to avoid naming issues
        if left(@FieldName, 1) <> '[' and right(@FieldName, 1) <> ']'
            set @FieldName = '[' + @FieldName + ']'
        -- Should we rtrim the field to deal with trailing spaces ... mostly for char fields
        if @RTrim = 1
            set @field = 'rtrim(' + @FieldName + ')'
        -- Create a temp table to hold our examples of each character matched
        if object_id('tempdb..##fieldCharacterSetTemp') is not null
            drop table ##fieldCharacterSetTemp
        set @sql = 'select * into ##fieldCharacterSetTemp from (select t1.* from ' + @TableSchema + '.' + @TableName + ' t1 inner join ' + @TableSchema + '.' + @TableName + ' t2 on 1 = 1 where 1 = 0) X'
        --Loop through the ascii characters low to high
        while @LowCharRange <= @HighCharRange
            --Build the from and where clauses
            set @sqlbuild = ''
            set @sqlbuild = @sqlbuild + 'from '
            if @TableSchema <> ''
                set @sqlbuild = @sqlbuild + @TableSchema + '.'
            set @sqlbuild = @sqlbuild + @TableName + nchar(13)
            set @sqlbuild = @sqlbuild + 'where ' + @field
            -- If the character is A-Z or a-z then case sensitive compare
            if (@LowCharRange between 65 and 90) or (@LowCharRange between 97 and 122)
                set @sqlbuild = @sqlbuild + ' COLLATE Latin1_General_CS_AS'
            set @sqlbuild = @sqlbuild + ' like '
            -- Deal with a few special SQL characters
            if @LowCharRange = 37 -- % character
                set @sqlbuild = @sqlbuild + '''%\%%'' escape ''\''' + nchar(13)
            else if @LowCharRange = 39 -- ' character
                set @sqlbuild = @sqlbuild + '''%''''%''' + nchar(13)
            else if @LowCharRange = 95 -- _ character
                set @sqlbuild = @sqlbuild + '''%\_%'' escape ''\''' + nchar(13)
                set @sqlbuild = @sqlbuild + '''%' + nchar(@LowCharRange) + '%''' + nchar(13)
            -- Buld main counting sql
            set @sql = ''
            set @sql = @sql + 'select @countOut = count(*)' + nchar(13)
            set @sql = @sql + @sqlbuild
            --Get count of rows that have the character
            execute sp_executesql @sql,N'@countOut bigint = 0 output',@countOut=@count output
            --If any rows have this character add it to the result set
            if @count > 0
                insert into @chars values (nchar(@LowCharRange))
                -- Build the select sql to look at example lines that have this character
                set @selects = ''
                set @selects = @selects + 'select top 1 *' + nchar(13)
                set @selects = @selects + @sqlbuild
                insert into @statements values (rtrim(replace(@selects, nchar(13), ' ')))
                set @sql = ''
                set @sql = @sql + 'insert into ##fieldCharacterSetTemp' +nchar(13)
                set @sql = @sql + @selects
            -- Next character
            set @LowCharRange = @LowCharRange + 1
        select * from @chars
        select * from @statements
        if object_id('tempdb..##fieldCharacterSetTemp') is not null
            select * from ##fieldCharacterSetTemp
            drop table ##fieldCharacterSetTemp