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

How to programmatically create a Jira issue link in Confluence that is mentioned in Jira?

Hi!

I have created some code that programmatically creates a Confluence page, with a formatted body from a Velocity template. This is working as expected and renders the page successfully. However, when you create a Jira Issue Macro in Confluence "normally" through the web interface, it is automatically added as a mentioned page in Jira? However, this is not happening when I create the page automatically.

Also, if I try to delete the macro and re-add it to the programmatically created page, it still doesn't work. This makes me suspect that something is wrong with the page creation. Have anyone encountered this issue before? Please see the code below:

Java Function that creates the Jira Macro:

public boolean createXrayReportInConfluence(String spaceKey, String pageTitle, String parentPageTitle, List<String> listOfJiraIssues) {
try {
ApplicationLink jiraApplicationLink = applicationLinkService.getPrimaryApplicationLink(JiraApplicationType.class);

Page page = new Page();
Page parentPage;

//if spaceParentPage equals "None", then set parentPage as the home page of the space
if (parentPageTitle.equals("None")) {
parentPage = spaceManager.getSpace(spaceKey).getHomePage();
} else {
parentPage = pageManager.getPage(spaceKey, parentPageTitle);
}

page.setSpace(spaceManager.getSpace(spaceKey));
page.setTitle(pageTitle);

Map context = MacroUtils.defaultVelocityContext();
context.put("jiraissues", listOfJiraIssues);
context.put("JiraPrimaryApplicationName", jiraApplicationLink.getName());
context.put("JiraPrimaryApplicationID", jiraApplicationLink.getId().toString());
String result = VelocityUtils.getRenderedTemplate("template/xraytestwizard.vm", context);

page.setBodyAsString(result.trim().replaceAll(" +", " "));
page.setVersion(1);
page.setParentPage(parentPage);

//Save the page
pageManager.saveContentEntity(page, new DefaultSaveContext.Builder().build());

//Add the newly created page as a child to the parentPage specified above
parentPage.addChild(page);

//Work-around in order to make the page appear in the space instantly
confluenceIndexer.reIndex(page);

return true;

} catch (Exception ex) {
log.warn("Caught Exception!");
log.warn(ex.getMessage());
log.warn(ex.toString());
return false;
}
}

The Velocity template:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<h3>
<span style="color: rgb(0,0,128);">Requirements (Stories) tested in this report</span>
</h3>
<ol>
#foreach ($jiraIssueKey in $jiraissues)
<li>
<span style="color: rgb(0,0,128);">
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="columns">key,summary,type,created,updated,due,assignee,reporter,priority,status,resolution</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
<ac:parameter ac:name="key">$jiraIssueKey</ac:parameter>
</ac:structured-macro>
</span>
</li>
#end
</ol>
<h3>
<span style="color: rgb(0,0,128);">Open Defects</span>
</h3>
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="columns">key,summary,type,created,updated,due,assignee,reporter,priority,status,resolution</ac:parameter>
<ac:parameter ac:name="maximumIssues">20</ac:parameter>
<ac:parameter ac:name="jqlQuery">(
#foreach( $jiraIssueKey in $jiraissues )
issue in defectsCreatedForRequirement("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) ORDER BY status ASC, priority ASC
</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
<h3>
<span style="color: rgb(0,0,128);">Test Summary</span>
</h3>
<table class="wrapped">
<colgroup>
<col/>
<col/>
</colgroup>
<tbody>
<tr>
<th colspan="1">Metrics</th>
<th colspan="1">Current value</th>
</tr>
<tr>
<td>Total no. of Test Cases</td>
<td>
<div class="content-wrapper">
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
)
</ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Red</ac:parameter>
<ac:parameter ac:name="title">Fail</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td colspan="1">
<div class="content-wrapper">
<p>
<ac:structured-macro ac:macro-id="2e1db40d-6c34-4857-bfbb-9077107fed7f" ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Fail </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td>
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:macro-id="64969a82-a16e-41a6-a9d4-62f16ee0a7a6" ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Green</ac:parameter>
<ac:parameter ac:name="title">Pass</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td>
<div class="content-wrapper">
<p>
<ac:structured-macro ac:macro-id="a60d6eff-1484-4cd7-826e-722599bf587d" ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Pass </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:macro-id="a9c6d66b-031b-4cbe-871f-e5843a743c84" ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Yellow</ac:parameter>
<ac:parameter ac:name="title">Executing</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td colspan="1">
<div class="content-wrapper">
<p>
<ac:structured-macro ac:macro-id="1e9a3aa6-ecae-42a9-9b4b-ac9ec78a8c8d" ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Executing </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td>
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:macro-id="e148d1c5-b932-4599-908f-cbc1dc4ba8d5" ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="subtle">true</ac:parameter>
<ac:parameter ac:name="title">Todo</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td>
<div class="content-wrapper">
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Todo </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="title">Aborted</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td colspan="1">
<div class="content-wrapper">
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Aborted </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Red</ac:parameter>
<ac:parameter ac:name="title">Blocked</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td colspan="1">
<div class="content-wrapper">
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Blocked </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="content-wrapper">
<p>No. of <ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Blue</ac:parameter>
<ac:parameter ac:name="title">Excluded</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
<td colspan="1">
<div class="content-wrapper">
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' AND (
#foreach( $jiraIssueKey in $jiraissues )
key in requirementTests("$jiraIssueKey") #if( $velocityHasNext ) OR #end
#end
) AND TestRunStatus = Excluded </ac:parameter>
<ac:parameter ac:name="count">true</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
</div>
</td>
</tr>
</tbody>
</table>
#foreach ($jiraIssueKey in $jiraissues)
<h3>
<span style="color: rgb(0,0,128);">
<strong>Test Execution Status for </strong>
</span>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="columns">key,summary,type,created,updated,due,assignee,reporter,priority,status,resolution</ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
<ac:parameter ac:name="key">$jiraIssueKey</ac:parameter>
</ac:structured-macro>
</h3>
<p>
<ac:structured-macro ac:name="jira" ac:schema-version="1">
<ac:parameter ac:name="server">$JiraPrimaryApplicationName</ac:parameter>
<ac:parameter ac:name="columns">key,summary,assignee,priority,testrunstatus,fixversions</ac:parameter>
<ac:parameter ac:name="maximumIssues">100</ac:parameter>
<ac:parameter ac:name="jqlQuery">issuetype = 'Test' and key in requirementTests('$jiraIssueKey') ORDER BY priority DESC </ac:parameter>
<ac:parameter ac:name="serverId">$JiraPrimaryApplicationID</ac:parameter>
</ac:structured-macro>
</p>
#end

1 answer

1 accepted

1 vote
Answer accepted

Hi @Johan Jonsson Nilsson ,

It was really interesting question. :)

I found that during Jira issue viewing /rest/viewIssue/1/remoteIssueLink/render/{id} is invoked which results in 500 HTTP Internal Server Error. At the same time there is next ERROR in confluence log:

java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:877)
at com.atlassian.confluence.api.model.content.History$HistoryBuilder.createdDate(History.java:231)

which points us to the page create date absence.

So the only thing you need to do is to set creation date:

page.setCreationDate(new Date()); 

Hi!


That seemed to help quite a bit, however now I get this error:

2019-12-19 10:55:20,511 WARN [Jira remote link executor:thread-54038] [plugins.jira.links.JiraRemoteLinkManager] lambda$executeRemoteLinkRequest$0 Failed to create a remote link to ISSUE-997 in Jira. Reason: 401 -

What can I do to fix this?

Hi!

Yes, I figured that it could be anything due to that. However, it seems that the user has the necessary permissions and I have also double checked so that it has approved access between Jira and Confluence.

However, I noticed this now:

 -- url: /confluence/rest/tss/testwizard/latest/resources/createxrayreport | traceId: 88e5d023782b8eb0 | userName: anonymous
2019-12-19 13:08:00,580 WARN [Jira remote link executor:thread-54310] [plugins.jira.links.JiraRemoteLinkManager] lambda$executeRemoteLinkRequest$0 Failed to create a remote link to ISSUE-595 in Jira. Reason: 401 -

Could it be that I need to do the request as that user, otherwise it will fail? If that is the case, how do I do that? 

First try to specify page creator:

page.setCreator(yourConfluenceUser);

If the first doesn't work, try to use AuthenticatedUserImpersonator:

AuthenticatedUserImpersonator.REQUEST_AGNOSTIC.asUser(() -> createXrayReportInConfluence()), yourUser);

The first one didn't work, but I will try the second one! 

I'll get back to you!

Thanks a lot! Now it works perfectly! :)

I noticed this in the log whenever I create a page, anything I should be alarmed about? The page creation seems to work fine..

2019-12-19 14:32:01,547 ERROR [com.atlassian.confluence.notifications.impl.DefaultDispatchService:thread-5] [persistence.dao.hibernate.HibernateContentPermissionSetDao] logAncestorsTableFailure Detected ancestors table corruption for pageId: 126182143. Access to this page is blocked for all users as inherited permissions cannot be determined. To resolve this, rebuild the ancestors table. See https://confluence.atlassian.com/display/DOC/Rebuilding+the+Ancestor+Table

Is 126182143 the id for your just created page?

Set the logging level of com.atlassian.confluence.pages.ancestors to INFO and run Repair the Ancestors Table job at  General Configuration > Scheduled Jobs.

Let's see the result of the job.

Yes, that is correct. I just ran the job and it said: 

2019-12-19 15:10:53,692 INFO [Caesium-1-3] [confluence.pages.ancestors.AncestorsRepairer] repairAncestors Ancestors have been repaired. Found and fixed 0 broken pages. It took 7 sec for 961 spaces, average space processing time 0 sec.

Unfortunately I can't tell exactly what's wrong here. It's seems that it's related to parent/child pages relationship. When is parent page created/last modified?

The parent page is created just before the actual page is created, in case it doesn't exist. However, the error seems to still happen when I create a new page with the parent page already created.

However, I searched on this issue and found this: https://community.atlassian.com/t5/Confluence-questions/logAncestorsTableFailure-after-create-spaces-and-pages-in/qaq-p/756363

By adding the following function right before saving the page, the error disappeared.

pageManager.updatePageInAncestorCollections(page, parentPage);

 Therefore, the final code looks like this:

public boolean createXrayReportInConfluence(String spaceKey, String pageTitle, String parentPageTitle, List<String> listOfJiraIssues) {
try {
ApplicationLink jiraApplicationLink = applicationLinkService.getPrimaryApplicationLink(JiraApplicationType.class);

Page page = new Page();
Page parentPage;

//if spaceParentPage equals "None", then set parentPage as the home page of the space
if (parentPageTitle.equals("None")) {
parentPage = spaceManager.getSpace(spaceKey).getHomePage();
} else {
parentPage = pageManager.getPage(spaceKey, parentPageTitle);
}

ConfluenceUser user = null;

if (userAccessor.exists("Support")) user = userAccessor.getUserByName("Support");

Date dateToUse = new Date();

page.setCreator(user);
page.setLastModifier(user);
page.setSpace(spaceManager.getSpace(spaceKey));
page.setTitle(pageTitle);
page.setCreationDate(dateToUse);
page.setLastModificationDate(dateToUse);

Map context = MacroUtils.defaultVelocityContext();
context.put("jiraissues", listOfJiraIssues);
context.put("JiraPrimaryApplicationName", jiraApplicationLink.getName());
context.put("JiraPrimaryApplicationID", jiraApplicationLink.getId().toString());
String result = VelocityUtils.getRenderedTemplate("template/xraytestwizard.vm", context);

page.setBodyAsString(result.trim().replaceAll(" +", " "));
page.setVersion(1);
page.setParentPage(parentPage);

pageManager.updatePageInAncestorCollections(page, parentPage);

//Save the page
pageManager.saveContentEntity(page, new DefaultSaveContext.Builder().build());

//Add the newly created page as a child to the parentPage specified above
parentPage.addChild(page);

//Work-around in order to make the page appear in the space instantly
confluenceIndexer.reIndex(page);

return true;

} catch (Exception ex) {
log.warn("Caught Exception!");
log.warn(ex.getMessage());
log.warn(ex.toString());
return false;
}
}

Thank you so much @Aleksandr_Zuevich for the help, really appreciate it! :)

Best regards,
Johan

Great! Thanks for sharing the final solution with us!

Like Johan Jonsson Nilsson likes this

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Posted in Confluence

What project did you transition or start on Confluence with the shift to remote work?

It’s been great to hear from fellow users over the last few weeks about the best tips and fun moments you’ve had working on Confluence since the transition to working remote. I’d love to keep the c...

32 views 2 4
Join discussion

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