Get hands-on training for JIRA Software, Confluence, and more at Atlassian Summit Europe. Register now ›

At Atlassian, all developers end up doing a 2 week support rotation every now and then. It’s a great opportunity to see first hand how all the buggy code I write as a developer ends up hurting customers and will hopefully provide an incentive to write less buggy code in the future ;). It is also a good time to come up with innovative little patches that help customers in the short-term, which can then be fed back into the product in the long run. This blog is about one such case.
During my last support stint one of our JIRA Studio support engineers brought a support case to my attention where customers where using the JIRA dashboard fairly heavily from iPhone and Android devices. Since their Studio instance got upgraded to JIRA 4.0, they could hardly use the dashboard anymore and apparently 2 gadgets were critical for their users in the field. The JIRA 4 dashboard includes a lot of javascript at the top of the page, and generates a lot of DOM elements dynamically with javascript. This generally isn’t a problem on most modern browsers, but on a mobile device with limited CPU and memory it can make a website crawl.
Read on for a one file solution to this problem.


Thanks to some decent REST endpoints I was able to retrieve all gadgets for a particular dashboard very easily with Javascript. I then wrote a simple JSP page that can be deployed without restarting JIRA which allows users to display a list of links for all the gadgets on a particular dashboard page. When the user then clicks on a gadget’s link, the gadget will open in a new window.
The page loads incredibly fast even on an iPhone since there’s hardly any Javascript to load (just jQuery) and barely any DOM elements to generate. The gadget itself also loads quite quickly since only the gadget contents have to be rendered.
The JSP is simply this:

<%@ page import="com.atlassian.plugin.webresource.WebResourceManager" %>
<%@ page import="com.atlassian.jira.ComponentManager" %>
<%@ page import="com.atlassian.plugin.webresource.UrlMode" %>
<html>
<head>
<%
final WebResourceManager webResourceManager = ComponentManager.getInstance().getWebResourceManager();
webResourceManager.requireResource("com.atlassian.auiplugin:jquery");
webResourceManager.includeResources(out, UrlMode.AUTO);
%>
<meta name="decorator" content="none">
<script type="text/javascript">
var contextPath = '<%=request.getContextPath()%>';
jQuery(function() {
var getUrlVars = function() {
var vars = [], hash;
var hashes = window.location.href.slice(
window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
};
var htmlDecode = function(input) {
var e = document.createElement('div');
e.innerHTML = input;
return e.childNodes[0].nodeValue;
};
var urlVars = getUrlVars();
var dashboardId = urlVars["d"];
jQuery.ajax({
url:contextPath + "/rest/gadget/1.0/currentUser",
dataType: 'json',
contentType:'application/json',
type:'GET',
success: function (data) {
jQuery("#loading").show();
jQuery.ajax({
url:contextPath + "/rest/dashboards/1.0/" +
dashboardId + ".json",
dataType: 'json',
contentType:'application/json',
type:'GET',
success: function (data) {
jQuery("#dashboardTitle").text(data.title);
jQuery(data.gadgets).each(function() {
jQuery("#results").append(jQuery("<h3><a href=\"" +
htmlDecode(this.renderedGadgetUrl) +
"\" target='_blank'>" +
this.title + "</a></h3>"));
jQuery("#loading").hide();
});
},
error: function(data) {
alert("Error retrieving dashboard with id '" +
dashboardId + "'");
}
});
},
error: function(data) {
jQuery("#login").show();
alert("Please login first before attempting to view dashboard!");
}
});
});
</script>
</head>
<body>
<a id="login" style="display:none;" href="<%=request.getContextPath()%>/login.jsp?os_destination=gadgets.jsp%3F<%=request.getQueryString()%>">Login</a></div>
<div id="loading" style="display:none;">Loading gadgets...</div>
<h1 id="dashboardTitle"></h1>
<div id="results">
</div>
</body>
</html>

The JSP, checks if the user is authenticated first (and if not displays an alert and a link to the login page) then tries to fetch all the gadgets for the dashboard with the id provided. It then displays the gadgets as a list of links that will open up in a new window.
Just in case anyone’s wondering: The main reason why gadget URLs can’t just be bookmarked in the first place is because a gadget’s render URL contains a security token that expires (after 1 hour) after which the gadget wont render any longer.
With the JSP above, users can bookmark a URL on their phones such as http://localhost:2990/jira/gadgets.jsp?d=10000 (where 10000 is the dashboard id) which will then provide them with a list of all the gadgets on that dashboard:
iphonegadgets.png
It’s not pretty, but if all you’re after is performance than this little hack works great. It’s certainly something I’ll be looking at integrating into my 20% time project, the JIRA iPhone web-interface (along with some improvements; iGoogle have a very nice mobile version of their dashboard and gadgets).

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now