Topics covered: Jira Integration, Confluence Storage Format, User Parameters, Sorting
Request
Sam Tang posted a question on 👉 community.atlassian.com
Hello Atlassian Community,
I want to list out a table to display some tickets' assignee history, I know I can get the information via 'activity tab' for a single ticket, but I would like a list of tickets in a table like this:
Ticket ID, ticket summary, assignee history
ID-1 ticket 1 Tom > Jerry > Shannon
ID-2 ticket 2 Jerry > Tom> Shannon
ID-3 ticket 3 A> B> C
Can you please help?
Thank you very much.
The Real Problem!
This macro solves a frequent visibility issue:
How do you track and display who was assigned to a Jira issue over time, directly within Confluence?
Perfect for:
Sprint retrospectives
Audits
Issue lifecycle analysis
Can User Macro deal with it? Let’s see…
Assignee History Macro
Step 1: Basic macro
This will be a static, block macro, without a body.
For customization, the JQL User Parameter is needed:
Use a String parameter (not required), with this default value: reporter = currentUser() AND updatedDate >= "-30d"
The following template will do
Make a Jira request
Print a list of issues with the assignee’s change history
## Search for issue with key, summary, and changelog included
#set ( $issues = $JiraManager.get("/rest/api/3/search/jql?fields=key,summary,assignee&expand=changelog&jql=${parameters.jql}").issues )
#foreach ( $i in $issues)
<p>$i.key:<br>
#foreach ( $h in $i.changelog.histories )
#foreach ($item in $h.items)
#if ($item.fieldId == "assignee")
$!item.fromString → $!item.toString<br>
#end
#end
#end
</p>
#end
! symbol is used to handle empty strings in values like $!item.fromString and $!item.toString
Without it, you might see errors like this on Unassigned issues:
To test the result, add the macro to the page using Macro Browser or by typing /Issue Assignee History.
JiraManager makes requests to Jira using OAuth authentication.
This requires a one-time authorization step to allow access.
Once access is granted, the template renders like this:
Step 2: Full Assignee History
Previously, only changes were shown. Now, we’ve added support for empty states and aligned changes into a single line.
If the JQL returns nothing, the macro includes a Check for an empty result to handle that and notify the user.
To make the assignee history a one-liner, the current Assignee is shown, followed by a list of $item.fromString values from the change history.
## Search for issue with key, summary, and changelog included
#set ( $issues = $JiraManager.get("/rest/api/3/search/jql?fields=key,summary,assignee&expand=changelog&jql=${parameters.jql}").issues )
## Check for an empty result
#if(!$issues)
No issues for JQL <code>${parameters.jql}</code></a>
#stop ## End of execution, there is nothing to render
#end
#foreach ( $i in $issues)
<b>$i.key:</b>
## Current Assignee
#if ($i.fields.assignee)
$i.fields.assignee.displayName
#else
<i>(Unassigned)</i>
#end
#foreach ( $h in $i.changelog.histories )
#foreach ($item in $h.items)
#if ($item.fieldId == "assignee")
#if ($item.fromString)
← $item.fromString
#else
← <i>(Unassigned)</i>
#end
#end
#end
#end
<br> ## new line for new issue
#end
Ctrl+S or Cmd+S to save the template
Ctrl+R or Cmd+R to reload the page and see the result
Step 3: Nice-looking Assignee History with clickable usernames
Changes in this step:
The current assignee moved to the end of the history
Arrows reversed from ← to →
History items sorted chronologically using SortTool:
$SortTool.sort($i.changelog.histories, ["created"])
To create Resource Identifiers links, usernames were replaced with AccountIDs using Confluence Storage Format:
<ri:user ri:account-id="${i.fields.assignee.accountId}"/></ac:link>
And finally, the Atlassian UI lozenge was used for the Unassigned state. The following class aui-lozenge-subtle makes it lighter:
<span class="aui-lozenge aui-lozenge-subtle">unassigned</span>
The whole new template:
## Search for issue with key, summary, and changelog included
#set ( $issues = $JiraManager.get("/rest/api/3/search/jql?fields=key,summary,assignee&expand=changelog&jql=${parameters.jql}").issues )
## Check for empty result
#if(!$issues)
No issues for JQL <code>${parameters.jql}</code></a>
#stop ## End of execution, there is nothing to render
#end
#foreach ( $i in $issues)
<b>$i.key:</b>
## Sort history items by creation date ascending
#set ($sortedHistory = $SortTool.sort($i.changelog.histories, ["created"]))
#foreach ( $h in $sortedHistory )
#foreach ($item in $h.items)
#if ($item.fieldId == "assignee")
#if ($item.from)
<ac:link><ri:user ri:account-id="${item.from}"/></ac:link> →
#else
<span class="aui-lozenge aui-lozenge-subtle">unassigned</span> →
#end
#end
#end
#end
#if ($i.fields.assignee)
<ac:link><ri:user ri:account-id="${i.fields.assignee.accountId}"/></ac:link>
#else
<span class="aui-lozenge aui-lozenge-subtle">unassigned</span>
#end
<br> ## new line for new issue
#end
And here’s how it looks on the page now:
Quite better. But something is missing…
Step 4: Table view with issue details
We need a $jiraURL variable to make the Issue key clickable. Jira URL is the same as Confluence’s $baseUrl, but without /wiki. Here’s how to construct the issue URL:
$baseUrl ## 1
$StringUtils.replace($baseUrl, "/wiki", "") ## 2
#set ( $key = "UM-555" )
${jiraURL}/browse/${key} ## 3
## Output
1> https://subdomain.atlassian.net/wiki
2> https://subdomain.atlassian.net
3> https://subdomain.atlassian.net/browse/UM-555
To prevent Issue Key from wrapping onto two lines, an inline style is needed:
<td style="white-space: nowrap;">
Aside from that, the table is pretty standard, now with an extra Summary column:
## Search for issue with key, summary, and changelog included
#set ( $issues = $JiraManager.get("/rest/api/3/search/jql?fields=key,summary,assignee&expand=changelog&jql=${parameters.jql}").issues )
## Jira URL is a Confluence baseURL, but w/o "wiki/"
#set ( $jiraURL = $StringUtils.replace($baseUrl, "/wiki", "") )
## Check for empty result
#if(!$issues)
No issues for JQL <code>${parameters.jql}</code></a>
#stop ## End of execution, there is nothing to render
#end
<table>
<thead>
<tr>
<th>Issue</th>
<th>Summary</th>
<th>Assignee history</th>
</tr>
</thead>
<tbody>
## Loop through issues
#foreach ( $i in $issues)
<tr>
<td style="white-space: nowrap;"> ## "nowrap" for issue key to be in one line
<a href="${jiraURL}/browse/${i.key}">$i.key</a>
</td>
<td>$i.fields.summary</td>
<td>
#if ( $i.changelog.histories )
## Sort history items by creation date ascending
#set ($sortedHistory = $SortTool.sort($i.changelog.histories, ["created"]))
#foreach ( $h in $sortedHistory )
#foreach ($item in $h.items)
#if ($item.fieldId == "assignee")
#if ($item.from)
<ac:link><ri:user ri:account-id="${item.from}"/></ac:link> →
#else
<span class="aui-lozenge aui-lozenge-subtle">unassigned</span> →
#end
#end
#end
#end
#end
## Current Assignee
#if ($i.fields.assignee)
<ac:link><ri:user ri:account-id="${i.fields.assignee.accountId}"/></ac:link>
#else
<span class="aui-lozenge aui-lozenge-subtle">unassigned</span>
#end
</td>
</tr>
#end
</tbody>
</table>
That’s it! The macro is now complete and has been uploaded to the Library: Issues Assignee History
The REAL problem has been solved — with User Macro for Confluence Cloud ![]()
