Raising issues in Jira is generally pretty straightforward if you have an account and are already logged in to Jira.  It becomes quite a pain however if you’re noticing a bug on a remote site somewhere and want to raise a bug in a Jira instance that you’re not currently logged in to or don’t have an account on yet.  This is the problem that the issue collector addresses.

Instead of having to:

  • Navigate away from the site where the bug occurred
  • Register for an account
  • Log in to Jira
  • Find the right project and issue type
  • Create the issue

You can now just:

  • Click a “Raise a bug” trigger on the remote site
  • Enter the bug details directly in a dialog that pops up on that site.

Here’s what it looks like in practice:
[youtube http://www.youtube.com/watch?v=kBP-y7HfA4M?rel=0]

Read on for more details from inception to implementation of this plugin!

Inception

A while ago, I set out to spend a 20% day to steal Confluence’s “Got feedback?” button and implement the same functionality in Jira. Confluence had been using  the  button quite successfully for quite some time to gather feedback both internal and external to Atlassian.  Their implementation used a Wufoo form embedded in an iframe on the page.  While this worked quite well in practice, it also had some drawbacks, mainly that it required someone to go through the Wufoo feedback manually and bring those issues into one of our internal Jira projects so that tasks could be passed along to the dev teams.

Following an ancient Jira tradition I set out to steal Confluence’s idea and implement it slightly better in Jira.  So in my 20% day I hacked up a Jira plugin that would insert some javascript on every page in Jira (using webresource contexts) creating the same trigger as Confluence.  When this trigger was clicked, instead of launching a Wufoo form, it would launch a Jira dialog that would submit its data back to a REST resource implemented by this plugin as well.  The REST resource would then send it’s data to http://jira.atlassian.com/ and raise issues in a particular project there.  All of this was hard-coded and kind of ugly but it was good enough to get feedback from our customers into http://jira.atlassian.com/.

Literally minutes after posting an internal blog about this change in Jira I had Mike and Sherif standing at my desk with suggestions to make this a more generic plugin. Born was the Issue Collector!  The idea was quite simple:

  • Allow project admins to configure a collector per project and issue type.  This would create a form to get user input, generate a unique ID for the collector and provide a javascript snippet to embed into a page
  • Users should not have to sign up for an account in Jira or even log in.  The collector would simply fall back to a configured fallback reporter if the user wasn’t authenticated
  • Minimal javascript to include in the remote website to reduce impact on page load times

Design

Initially I started out with some wireframe designs on paper to get a better understanding of how users would create custom collectors (unfortunately these were lost in a recent desk cleanup).  Doing them on paper was a great way to iterate very quickly over a number of versions of the design.  Doing this also helps you get a much better understanding of the problem and figuring out all the details that will be hard to implement later-on.  I then took these designs to a number of our internal designers (thanks Ross and Simon!) to get some early feedback before I even started building things.

Once I started implementing the plugin I published it as often and as early as possible both internally and externally.  Feedback I received through reviews was particularly useful and ended up having quite a big impact on the design.

Implementation details

There’s essentially 3 parts to this plugin:

  • The javascript to embed in the remote website we’re collecting issues from
  • The REST resources to receive the feedback (and render the forms in the iframe)
  • Admin UI to configure issue collectors

The javascript to embed on the remote website had the most unknowns and so I tackled that first.  Here’s an overview of how it all works in practice:

  1. The javascript inserted on the remote site is served by Jira. It will render a trigger on the parent page
  2. When this trigger is clicked the javascript then creates a dialog with an iframe that points back to a REST resource on Jira.  It’s on a different domain but that’s ok since the contents will be loaded in an iframe
  3. The iframe makes a request to the REST resource provided by the plugin in Jira
  4. The REST resource gets the collector id from the URL and makes a request to the CollectorService component provided by the plugin
  5. The CollectorService will retrieve the collector with a matching id from the CollectorStore component and then return the collector details to the REST resource
  6. The REST resource will render the template associated with the collector and return the HTML to the iframe in the remote website
  7. When the user hits “Send” the form gets POSTed to another REST resource provided by the Issue Collector plugin that allows anonymous access
  8. That REST resource then creates the issue using Jira’s IssueService.  At this stage the REST resource has set a reporter previously configured for the collector to make sure the issue will get created successfully even anonymous users can’t create issues in this Jira instance

The javascript inserted into the parent page is quite small. It’s essentially jQuery and 4.3 Kb of custom javascript to render the dialog outline and trigger.

One challenge presented by the iframe approach is that Javascript doesn’t allow cross domain access. In other words if we want to collect certain information from the parent page (such as browser type, URL, screen resolution…) it would normally not be possible to pass this to the iframe that is dynamically inserted into the page since its domain is Jira’s domain and therefore different to the parent page.  The Issue Collector gets around this by using the postMessage spec which is now finally implemented by all browsers that we support at Atlassian.

Essentially postMessage lets you send information from one iframe to another even if they are on different domains!

In the issue collector the bootstrap.js which is embedded into the parent page contains this line:
iframeElem[0].contentWindow.postMessage(feedbackString, "@baseUrl");

feedbackString in this case contains information we want to include in the issue from the parent page such as URL, screen resolution and browser type.  The @baseurl token gets replaced by a web-resource transform to be Jira’s baseurl.  It effectively limits what iframes on the page can receive the information.

Then in the collector.js inside the iframe we have this snippet to receive the information:

AJS.$(window).bind("message", function(e) {
// *********************** DANGER *******************
//CAREFUL! NEVER EVER eval any text here or it's a massive security hole. Always ensure it's properly
//escaped before inserting into the DOM!
AJS.$("#webInfo").val(e.originalEvent.data);
// *********************** DANGER *******************
});

We bind to the “message” event that postMessage produces and insert the string into a hidden input with the id “webInfo”. This could be a security hole if the string that’s passed in is not properly escaped or eval’ed, which would allow an attacker to run arbitrary javascript with Jira’s credentials (since the iframe is served on Jira’s domain).

That’s all for now…

There’s obviously a lot more implementation details I could write about but this blog post is already too long.  Feel free to check out the source of the plugin on BitBucket or download it here!

You’ve got issues! Now you can collect them.