Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

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).

Extra Bonus (December 2021 Update)

So your macro has been working fine now for some time, but your boss comes in and wants you to add two new statuses to the listing, "On Hold" and "Cancelled". But the macro only supports five colors. However, there is the Use lighter lozenge color option in the macro that uses a lighter lozenge background color with colored text. This option is controlled via a new macro parameter subtle with the values of true/false:

<ac:parameter ac:name="subtle">true</ac:parameter>

Now with this new option, we can use normal gray for Hold and subtle gray for Cancelled:

On-Hold.pngCancelled.png

First we need to update the exiisting macro paramater selections as well as the key map pairs to add these two new statuses:

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

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

Next we need to create a new key map to handle the subtle parameter settings, plus logic to select the setting:

#set($priorityoutline = {"Critical" : "false", "High" : "false", "Medium" : "false", "Low" : "false", "On Hold" : "false", "Cancelled" : "true"})


#set($prioritySubtle = $priorityoutline.get($paramPriority))

Then put it all together, adding the logic for the sublte parameter:

## Macro title: Priority
## Developed by: Bill Bailey, MarketCom, LLC
## Date created: 2021.12.02
## Version: 2.0

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

## Set value pairs for status macro instances
#set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue", "On Hold" : "Grey", "Cancelled" : "Grey"})
#set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4", "On Hold" : "5", "Cancelled" : "6"})
#set($priorityoutline = {"Critical" : "false", "High" : "false", "Medium" : "false", "Low" : "false", "On Hold" : "false", "Cancelled" : "true"})

#set($priorityColor = $priorityvalues.get($paramPriority))
#set($prioritySubtle = $priorityoutline.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="subtle">$!prioritySubtle</ac:parameter>
  <ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
  <ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
</span>

Now we have a way of handling up to 10 total priority values with our macro, and can go back to online shopping!

19 comments

Marc Dvorschak January 14, 2019

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
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 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. :)

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 14, 2019

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. ;-)

Jim Fancher January 18, 2019

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

Jerri Knipstein April 18, 2019

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

Fabienne Gerhard
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 22, 2019

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!

wesley_geddes May 5, 2020

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

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 6, 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

wesley_geddes May 6, 2020

@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). 

Brian_Mihok June 17, 2020

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!

Gonchik Tsymzhitov
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 2, 2021

That excellent article!

Jennifer Greiner November 18, 2021

Is there a list of available colors one can use? For example, I am trying to mimic the 'use lighter lozenge' option available in the Status macro, but am not sure what value to provide for the color.

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 18, 2021

@Jennifer Greiner yes you could. You could either a) hard code the macro to always use that option, b) provide a check box for that option, or c) add listing such as "Red Lighter" with logic to toggle that option.

Jennifer Greiner November 19, 2021

@Bill Bailey I was trying to hardcode one status option to include subtle=true but am running into issues. 

 

#set($priorityvalues = {"Pre-Plan" : "Grey", "Launch Planning" : "Blue", "Pilot" : "Blue"})
#set($priorityorder = {"Pre-Plan" : "1", "Launch Planning" : "2", "Pilot" : "3"})
#set($prioritysubtle = {"Pre-Plan" : "false", "Launch Planning" : "true", "Pilot" : "false"})

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

<span style="color:#FFFFFF;font-size:10px;margin-right:-5px;">
$!priorityorder.get($paramPriority)
$!prioritysubtle.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>

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 19, 2021

@Jennifer Greiner yes, you have a bit of a mess. ;-) Let me take a look and get back to you.

Kim Lees December 2, 2021

@Bill Bailey & @Jennifer Greiner - I am very interested in this as well. Any update?

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 2, 2021

I am actually working on an update now! Stay tuned.

Kim Lees December 2, 2021

@Bill Bailey Awesome! Thank you!!

Roman Hladky February 23, 2023

Hi all. This is a great article. But I have a slightly different problem. In my user macro I would like to "post-process" output of the macro, which is wrapped inside my own macro. I mean to take HTML output of the inner macro (like the status macro in this example) and do some manipulation (string replacement etc.). Is there a way how to do it?

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events