아이폰에서 JIRA가 느리나요?

도와주세요! JIRA 4 대시보드가 모바일 장치에서 느려요!


Andreas KnechtJIRA 에 대해 이야기 합니다. (2010년 1월)


Atlassian에서는 주기적으로 개발자들이 2주의 지원 로테이션을 처리합니다.


이것은 본인이 개발자로서 작성한 버그 많은 코드가 결국 고객들에게 어떻게 불편함을 주는지 직접 확인할 수 있는 좋은 기회이며, 또한 향후 더 버그가 적은 코드를 작성하는데 자극을 줍니다.


그리고 단기적으로 고객을 돕고 결국 제품에 피드백 될 수 있는 패치코드를 작성할 수도 있는 좋은 시간입니다. 이 블로그는 이러한 사례에 대한 것입니다.


지난 번 나의 서포트 시간에 한 엔지니어가 고객 불편사항인 아이폰과 안드로이드 모바일장치에서 JIRA 대시보드가 매우 느리다는 내용을 제게 가지고 왔습니다.


JIRA 4 대시보드는 페이지 앞단에 매우 많은 Javascript를 포함하고 있고 이 자바스크립트를 통해 동적으로 많은 DOM 항목을 생성합니다.


사실 이것은 일반적인 브라우저에서는 문제되지 않지만, CPU나 메모리가 제한된 모바일장치에서는 매우 느리게 동작될 수 있습니다.


이 문제에 대한 하나의 파일을 확인해 보십시요.잘 정리된 REST endpoint 덕분에, 저는 자바스크립트로 매우 쉽게 특정 대시보드의 모든 가짓(gadget)을 검색할 수 있었습니다.


그리고, JIRA를 재시작하지 않고도 사용자가 특정 대시보드 페이지의 모든 가짓에 대한 링크 목록을 볼 수 있도록 하는 간단한 JSP 페이지를 작성하였습니다.


사용자가 가짓의 링크를 클릭하면, 가짓은 새로운 윈도우에서 열릴 것입니다.이 때 페이지는 심지어 아이폰에서조차도 엄청나게 빠른 속도로 로드될 것입니다.


그것은 페이지 로드를 위해 자바스크립트를 사용해 DOM 항목을 생성하지 않고 단지 jQuery를 사용하기 때문입니다.


가짓 자체 또한 자체의 컨텐츠만을 표시하기 때문에 상당히 빠르게 로드됩니다.JSP 는 다음과 같이 간단합니다:



<%@ 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>

JSP는 우선 사용자가 인증되었는지를 확인 (인증되지 않으면 경고문구를 내보내고 로그인 페이지 링크를 보여줌) 하고 대시보드를 위한 모든 가짓을 제공된 ID와 함께 가져오려고 합니다.그리고 새로운 윈도우에서 열릴 가짓의 전체 목록을 생성해 냅니다.


궁금하신 분들을 위해: 가짓 URL이 애초에 북마크 될 수 없는 주요 원인은 가짓의 렌더링 URL이 나중에 (1시간 후) 만료 되는 보안 토큰을 포함하고 있기 때문입니다.위의 JSP를 통해 사용자는 자신의 휴대폰에 URL을 http://localhost:2990/jira/gadgets.jsp?d=10000 와 같이 북마크 할 수 있습니다. (1000 은 대시보드 ID) 그리고 이것은 사용자에게 대시보드의 모든 가짓 목록을 보여줄 것입니다.



그리 썩 좋지는 않지만, 이 방법은 효과가 있을 것입니다. 그리고 이것을 금번에 나의 20% 프로젝트인 JIRA 아이폰 web-interface (물론 일부 개선을 포함: iGoogle 은 정말로 대시보드와 가짓에 대한 훌륭한 모바일 버전을 가지고 있으니까...)


(골드피처 주)


국내에서도 이제 아이폰을 사용하는 사람의 수가 많이 늘어나면서 그동안 지지부진했던 모바일 웹의 활용에 관심이 커지고 있습니다.
본 글은 단순히 JIRA를 아이폰이나 안드로이드폰에서 접속해 사용하기 위한 팁을 서술한 기술적인 내용 입니다만, 한가지 더 눈에 띄는 것은 저희 Atlassian 본사에서 시행하는 개발자의 주기적 고객지원 업무 할당입니다.


국내에서 개발자들의 근무환경이 열악하다는 것은 주지의 사실이지만, 개발환경에 대한 문제를 떠나 개발자들이 얼마나 고객을 생각하며 개발에 임하는지에 대한 것도 되돌아보게 하는 글이 아닐까 합니다.


고객을 생각하는 개발자 마인드는 단순히 상품을 기획하는 사람들만의 것은 아닌듯 합니다.


한편으로는, 저렇게 개발자에게 고객지원 업무도 주기적으로 할당하는데 (우리나라에서는 개발할 시간도 없어 날밤 세우는데) 개발자들의 개발 생산성이 우리보다 나아 보이는 것은 많은 것을 생각하게 합니다.

댓글

이 블로그의 인기 게시물

시스템에 숨어있는 "윤초" 버그에 대해 준비하십시요

Confluence 내의 스프레드 시트 기능이 필요하시다면 애드온을 활용해 보십시요

Confluence 페이지의 분류와 관련된 잘 몰랐던 기능 3가지를 확인해 보십시요