It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Getting Started with User Macros - Writing a Wrapper Macro

First a note about the intent of this series of articles (this is the first —hopefully). While there are resources that talk about writing user macros, they typically talk about all that surrounds the macro, syntax, and give a simple "hello world" example. The problem with user macros is that they are coded using Velocity Tempting Language (VTL), which make use of Java constructs. VTL is obscure enough (and hard to Google as the name is a common word), that there are not many online tutorials. This series is intended to help those wanting to write user macros in getting started with some more complex (and useful) examples. On to the article!

A Wrapper Macro

Often with Confluence, there is a Confluence macro you love, but really need it to work differently than planned, or need a way to control its use (especially if you are an admin, or tech writer, for example). One solution is to create a wrapper macro – a user macro that wraps a Confluence macro, providing a way to control the parameters and possibly perform additional processing. Motivations for writing a macro are:

  • You are lazy and tired of having to set a lot of parameters (or having to correct the settings for other users).
  • Wanting to create a standard configuration of a macro, with a set of defaults that differ from the Confluence defaults.
  • Provide users with a limited set of variable parameters to force consistency of content.
  • You need to process the output of the macro before rendering.

A Candidate for a Wrapper Macro

A good candidate for a wrapper macro is the status macro. The status macro allows you to set label text and color to create a nice colored lozenge. For example:

All_Good.png

 

This macro is good for creating dashboards for reporting on status. Atlassian provides a good sample application in their article, How to build a release planning page in Confluence. The problems with the existing status macro for this use-model are:

  • It requires users to correctly type the status each time (and only use the preapproved statuses). For example, it is very easy to get users entering "IN-PROGRESS",  or "IN PORGRESS" instead of the intended entry. Or decide to create a new custom status "WIP" because they think it is a more accurate description. This variability is an issue when trying to roll up statuses from a number of users to a dashboard.
  • Colors have to be set each time, leading to users selecting different colors for the same status (or not setting a color at all) – again annoying when trying to create a dashboard.
  • There is also a style option with the macro (solid or outlined). Again, this option can lead to variability in lozenge appearances for the same status.

Creating a Status Wrapper Macro

Defining What the Macro Will Do

One use of the a status macro is for indicating priority, for example, critical, high, medium, low – with associated color coding (management loves colors!). Rather than requiring/hoping that users only use these four labels and the correct colors associated with each, our macro will just have a pull-down with the four statuses. No typing; no color selection — quick, easy and consistent.

Obtaining the Storage View of the Status Macro

Key to starting a wrapper macro is obtaining the storage view of a macro. The easiest way to obtain the storage view is to create a test page, insert the macro set to one of the desired configurations, then select View Storage Format from the page menu. For example, the storage view of the status macro configured for a critical status is as follows:

  <ac:structured-macro ac:macro-id="4e9a977c-c6cd-48d1-8381-e4f22e73e215" ac:name="status" ac:schema-version="1">
    <ac:parameter ac:name="colour">Red</ac:parameter>
    <ac:parameter ac:name="title">Critical</ac:parameter>
  </ac:structured-macro>

For our use, we need to set two parameters: colour and title.

Note: The macro attribute ac:macro-id can (and may need to) be removed. It is a unique identifier for a macro instance.

Defining the UI

Since the goal of this wrapper macro is to restrict the options to a defined set, we want the user to only be able to select from one of the defined priorities (the associated color will be set in macro code). The needed macro code for creating a pull-down with a set number of conditions is:

## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low

 Note: There should be no spaces in the list of enumerated values. In other words, "Critical" is different from "Critical ". For details on user macro syntax, see User Macro Template Syntax.

PriorityGUI.png

First Pass on the Macro Body

With a defined parameter, we can now substitute this parameter in our status macro.

 <ac:structured-macro ac:name="status" ac:schema-version="1">
  <ac:parameter ac:name="colour">Red</ac:parameter>
  <ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>

Note: The exclamation point in the variable is Velocity quiet notation.

Our macro will now update the status macro lozenge text, based on the parameter selected by the user. But the colo(u)r value is still static, so a parameter is needed for this setting, for example:

<ac:structured-macro ac:name="status" ac:schema-version="1">
  <ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
  <ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro

But we need to set the value of $priorityColor based on the value of $paramPriority and not force the user to select it.

Deriving the Value of $priorityColor

We could use an if-elseif tree to set the color value for our wrapper macro, but a more compact and less error-prone way to set this value is via lookup. Velocity supports Java methods and properties, allowing for the creation of a key-value map via the Java Map class. In essence, we create a list of key-value pairs that then can be used as a look-up table later. For our priority macro, the key-value map that sets the various macro colors based on the priority value is:

 #set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue"})

Then setting the value for $priorityColor is then done by looking up (via the get method) the corresponding value:

#set($priorityColor = $priorityvalues.get($paramPriority))

Putting it All Together

Putting the entire macro together, along with adding headers (best practices):

## Macro title: Priority
## Developed by: Bill Bailey, MarketCom, LLC
## Date created: 2019.01.12
## Version: 1.0

## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low

## Set value pairs for status macro instances
#set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue"})
#set($priorityColor = $priorityvalues.get($paramPriority))

<ac:structured-macro ac:name="status" ac:schema-version="1">
  <ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
  <ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>

Follow the instruction spelled out in Writing User Macros for installing the macro code. Be sure to select No Macro Body in the settings. Voilá! You have a priority macro.

Bonus Points

I deployed this macro inside a Page Properties Macro table within a page template. As users created pages using this template, a summary report (dashboard) was created using the Page Properties Report Macro. This macro creates a table pulling the properties from all the child pages, creating a management dashboard.

Within a few days of deploying this macro to my user base, they asked if there was a way to have a custom sort order. The table created by Page Properties Report Macro can be sorted alphanumerically, but that results in the priority being sorted as Critical, High, Low, then Medium — as situation deemed unacceptable by management! The easiest solution would have been to just add numbers to all the priorities, for example 1-Critical, 2-High, 3-Medium, 4-Low — a result that is cosmetically ugly. But we have our wrapper macro which allows us to add additional code.

While it might be possible to add Javascript to a page to create a custom sort order, I am both lazy and not Javascript literate enough to generate and deploy such code. The solution I settled on was to take the simple solution a step further and add the numbering outside of the status macro (and then hide that number using CSS trickery).

Again, using a key-value-map construct to associate a numbering with a priority:

#set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4"})

Then wrapping the status macro with a span tag plus $priorityorder:

<span>$!priorityorder.get($paramPriority)
<ac:structured-macro ac:name="status" ac:schema-version="1">
  <ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
  <ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
</span>

Now our sorting problem is fixed, but the result is not really what we want:

Priority2_ugly.png

Adding some inline styling to the span tag to apply the white-tape approach (make the number white to render it invisible), plus dropping the font size down and setting negative margin to get better alignment, our final code now appears as:

## Macro title: Priority
## Developed by: Bill Bailey, MarketCom, LLC
## Date created: 2019.01.12
## Version: 1.1

## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low

## Set value pairs for status macro instances
#set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue"})
#set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4"})

#set($priorityColor = $priorityvalues.get($paramPriority))
<span  style="color:#FFFFFF;font-size:10px;margin-right:-5px;">$!priorityorder.get($paramPriority)
<ac:structured-macro ac:name="status" ac:schema-version="1">
  <ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
  <ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
</span>

The resulting macro renders as:

Priority2_final.png

Now end users are happy and so is management (and we can get back to surfing the web).

 

 

10 comments

Thanks for this good article. Easy to understand and with a very useful example. I'm looking forward to the next part of your series.

Davin Studer Community Leader Jan 14, 2019

Excellent write-up @Bill_Bailey. User macros are in my opinion a hidden gem inside Confluence. There are so many cool things you can do with them. Also, clever idea with the sort order. I would probably have over engineered it with JavaScript. :)

Thanks @Davin Studer, you are right about user macros. I and many users coming to Confluence complain about X or Y being missing, but with user macros, you can often fill in those holes. Yes, it would be nice if Confluence had X or Y builtin, but at least they give you access to a very powerful tool to build a solution.

And on the over-engineering, remember my statement that I am lazy. ;-)

Yes, excellent Bill.  Thanks for knocking this out.  I'm gonna try it this weekend.

Excellent article.  Thanks for publishing!  It is very helpful. 

Thanks @Bill_Bailey

for this great article - I love the opportunity given by usermacros. How could I miss this great article - already looking forward gain more experiences.

Thanks to @Davin Studerfor sharing this article!

Is it possible to allow the end user to provide their own values for the dropdown list? 

Bill_Bailey Community Leader May 06, 2020

@wesley_geddes, yes you could make that field a text field rather than a pull-down. But part of the point was to create some standardization of the statuses. The available statuses could be change easily changed by a Confluence admin, though

@Bill_Bailey Right. I was thinking of a way to get around needing to create a new user-macro for each drop-down a user may want for different reports. I can see that doing it would defeat the purpose of standardizing these statuses. Let the process owner decide on the statuses, and hope they don't make too many changes. 

Thanks for the great article! I was able to add more options by adding a third parameter that determines if the status is filled or not (doubles the amount of colors available). 

Terrific article!  Thank you for writing it. 

I have a similar problem to what you addressed, but it is slightly different.  I wrote up a question here:  https://community.atlassian.com/t5/Confluence-questions/Can-I-specify-parameters-to-use-in-the-Jira-Issue-Filter-macro/qaq-p/1407882.

I want to extend the Jira Issue/Filter macro, which you clearly addressed, but I'd like to do so with a combo box that lives within the Confluence page, not within the macro definition.  The reason is that I want users visiting the page to be able to change the values and perhaps click a refresh button, which then triggers the Jira query.

I found the following post on creating a combo box within a page, but I don't have much experience with JavaScript (I have a Java background) and I'm brand new to Confluence Macros:  https://community.atlassian.com/t5/Answers-Developer-Questions/Creating-a-dropdown-user-macro/qaq-p/494678.  Any assistance you could provide on moving the combo box outside the macro definition would be greatly appreciated!

Comment

Log in or Sign up to comment
TAGS
Community showcase
Published in Confluence Cloud

What's New in Confluence Cloud – June 2020 Edition

Ready for the monthly rollup of what happened in May for Confluence Cloud? Improved mobile login experience (& SSO) For those of you with the Confluence mobile app, we know the login experien...

27,683 views 41 90
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you