Embed Confluence pages in Jira issues

[Updated 2014-05-15 Adapt the iframe’s width as well and add a “edit in confluence link”]

There is a question in Atlassian’s Q&A tracker that hasn’t sufficiently been answered yet and that I stumbled upon today as well, namely embedding whole Confluence pages in Jira description (and any other rich-text enabled) fields.

The reason why one would want to do something like this is to avoid context-switching between both tools. Imagine you write your specification in a wiki, but want to use an issue tracker to manage your workload. And while Atlassian has a solution to link Jira issues to Confluence pages, there is no macro or other function to actually embed the content.

What I’ll show you today is basically a hack. We’ll generate a small HTML snippet dynamically in confluence that includes an <iframe> element and let the user copy that to a Jira description field where it loads the page’s print view. This hack was tested under the following (software) conditions:

  • Jira 6.1.7
  • Confluence 5.4.2, with the documentation theme globally applied
  • both setup under the same domain, i.e. yourserver.com/jira and yourserver.com/confluence

Now to the configuration. On Jira’s side only one thing is needed, the {html} macro must be enabled. By default, this is disabled for security reasons (you can tell) and you should really only enable this if your Jira instance is not publicly available. Anyways, follow these steps:

  1. In Jira, go to Manage Add-Ons
  2. Then change the “Filter visible addons” drop down to show “All Add-Ons”
  3. Then search for the word “wiki” and expand the Wiki Renderer Macros Plugin
  4. Then click on the link on the right which says “7 of 8 modules enabled”
  5. Finally, click “enable” next to the last module which says “html”

Now, on Confluence’ side we want to generate some HTML snippet for a specific page and we need some little UI for that. Usually, if you want to change the contents of a web page after it is rendered in the browser, you use some browser-specific mechanism, i.e. you write a Chrome extension or a Greasemonkey script for Firefox. But Confluence offers a better, cross-browser way to inject custom code – custom HTML!

  1. In Confluence, go to Custom HTML
  2. Click on “Edit” and paste the following code into the “At end of the HEAD” textbox
  3. Hit “Save”

Now to the code:

<script>
AJS.toInit(function(){
  var meta = AJS.$('meta[name=ajs-page-id]');
  if (meta.size() > 0) {
    var list = AJS.$('<li class="ajs-button normal" />')
      .appendTo('#navigation ul');
    AJS.$('<a rel="nofollow" accesskey="q" title="Copy embed code (q)" />')
      .text('Embed code')
      .on('click', function() {
        window.prompt('Embed code: Ctrl+C, Enter', '{html}\u003Cscript type="text/javascript">function aR(fr){$f=AJS.$(fr);$f.height($f.contents().height());$p=$f.parents("*[data-field-id=description]");if($p.length>0){$f.width($p.width())}else{$p=$f.parents("div.mod-content");if($p.length>0){$f.width($p.width()-30)}}}\u003C/script>\u003Ca href="/confluence/pages/editpage.action?pageId=3375112" style="float: right" target="confluence">\u003Csmall>Edit in Confluence \u003C/small>\u003C/a>\u003Ciframe src="/confluence/plugins/viewsource/viewpagesrc.action?pageId=3375112" style="overflow:hidden;border:0" onload="aR(this);">\u003C/iframe>{html}');
      })
      .appendTo(list);
  }
});
</script>

So what is this? Basically, AJS is Atlassian’s entry point for its AUI library (Atlassian User Interface). This contains a full version of jQuery, accessible via AJS.$. Now, if AJS is initialized, we query the page ID of the currently viewed page. This is embedded in the page as meta tag with the name “ajs-page-id”.

Next, a new button is added to the main navigation that opens a window prompt containing the HTML code to be copied (\u003C is <, this was needed to render a valid HTML page, while still showing proper HTML tags in the prompt).

Lets have a closer look at the dynamic code part that is later executed in Jira, this time broken down into lines, for better understanding:

{html}
<script type="text/javascript">
function aR(fr) {
  $f = AJS.$(fr);
  $p = $f.parents("*[data-field-id=description]");
  if ($p.length > 0) { 
    $f.width($p.width());
  } else {
    $p = $f.parents("div.mod-content");
    if ($p.length > 0) { 
      $f.width($p.width() - 30);
    }
  }
  $f.height($f.contents().height());
}</script>
<a href="/confluence/pages/editpage.action?pageId=' + meta.attr('content') + '" 
      style="float: right" target="confluence">
   <small>Edit in Confluence</small>
</a>
<iframe
  src="/confluence/plugins/viewsource/viewpagesrc.action?pageId=' + meta.attr('content') + '" 
  style="overflow:hidden;border:0"
  onload="aR(this);">
</iframe>
{html}

You can see some Javascript again, a link to edit the page externally in Confluence, and an iframe definition. The frame loads a confluence’ page source view (usually accessible from Tools > Show page source) and is set dynamically to the width of the outer container i.e. either the detailed Jira issue view’s div.mod-content container or Jira Agile’s description container (targetable with dd[data-field-id=description]) in a Scrum-based board. For the former we have to subtract some pixels to avoid that the edit bar on the right of the description is pushed outside of the parent container.

Now, to avoid that the contents of the iframe must be scrolled separately from the browser’s viewport, we also set the height of the iframe, in this case dynamically to the height of the iframe’s contents, as soon as these are loaded. Note that a pure CSS solution, like height: 100% or else, would not work here, because we’re not under control of the parent HTML containers in which the iframe is actually rendered and giving the iframe a fixed height would be nonsense as well, since you don’t know the page length in advance.

And thats it, now you can embed Confluence pages in Jira issues with only a few clicks! Have fun!