1. 程式人生 > >Atlassian In Action-Jira之二次開發(五)

Atlassian In Action-Jira之二次開發(五)

到現在已經寫到了第五章節,實際上離Jira的官方系統已經越來越遠,本章節的內容基本上已經完全脫離了Jira這個系統本身,而是依賴Jira的API介面和資料庫進行開發了。主要包含如下幾個功能:

  • 人員任務排期管理
  • 歷史人員任務排期檢查
  • BI報表

注意:
由於我們的二次開發基本都是做成靜態頁面,但是大量使用了Jira的API介面,為了能夠方便的使用。所以我們將這些頁面放到Jira的容器當中,在其中建立了一個目錄。這樣就能夠和Jira在同一個域名下,只要Jira系統有登入,訪問介面都是不需要額外進行認證的。

人員任務排期管理

背景

這個頁面的需求實際上是源自於BigGantt這個外掛,我們前面講過這是一個非常強大的外掛,對於迭代的任務管理有很大的幫助。

但是在慢慢的使用過程中產生了一個管理的場景:有一個重點專案佔用了一部分的研發力量,需要了解這部分的任務安排情況。並且當任務有變更的時候,能夠快速瞭解是否還有研發資源可以安排調整。
分析之後我們瞭解到,實際上這個需求有兩個重點,一要有一個任務列表(通常是一個JQL語句篩選出的主任務列表),二要有涉及的人力資源和子任務清單。BigGantt是否滿足呢?它可以根據JQL篩選出任務列表,但是這個列表只能夠以任務為視角展開。當我們需要評估某個人員排期如何,是否能夠插入或者調整時,沒有太好的介面來清晰的展示。

實現

所以我們設計瞭如下的功能:

可以看到這個介面分為三部分
第一部分是文字輸入框,用於執行JQL語句。

第二部分是主任務列表。
第三部分是人力資源甘特圖。
任務列表通過

https://jira.yourdomain.com/rest/api/2/search?jql=' + jqlstr + '&maxResults=9999

介面就可以獲取任務的json資料。

人力資源甘特圖是寫了一個python指令碼啟動了一個web容器,從jira的底層資料庫讀取資料,組織返回json。獲取的就是所有研發中心人員未完成的子任務。為什麼要用python?因為Jira這臺伺服器上沒有其他容器,如果要安裝tomcat之類的太麻煩,還要做一個mvc框架之類的東西或者直接寫jsp,太麻煩了。
下面給出獲取資料的sql:

         select concat(a.id) as pissueId,
                ifnull(concat("EXEPD-", k.issuenum),"無父任務") as pikey,
                k.SUMMARY as pisummary,
                concat("EXEPD-", a.issuenum) as ikey,
                '子任務' as itype,
                a.SUMMARY,
                b.pname,
                ifnull(ifnull(i.vname, e.customvalue), '版本為空') as vname,
                ac.last_name,
                ad.lower_parent_name,
                (case ad.lower_parent_name
                   when 'org-pd-qa' then '測試'
                   when 'org-pd-frontside-h5' then '前端h5'
                   when 'org-pd-frontside-native' then '原生'
                   when 'org-pd-serverside-b' then '業務'
                   when 'org-pd-serverside-a' then '架構'
                   when 'org-pd-product' then '產品UI'
                   else '' end) as deptname,
                DATE_FORMAT(a.CREATED,'%Y-%m-%d') as creatdate,
                DATE_FORMAT(a.DUEDATE,'%m/%d/%Y') as enddate,
                DATE_FORMAT(cd.DATEVALUE,'%m/%d/%Y') as startdate,
                concat(ab.ID) as asid,
                concat(ad.parent_id) as dpid,
                concat(ROUND(ifnull(a.TIMESPENT,0) / 3600, 1)) as timespent,
                ab.lower_user_name asname,
                1 as nums
         from jiraissue a
                join app_user ab on ab.user_key = a.ASSIGNEE
                join cwd_user ac on ac.lower_user_name = ab.lower_user_name
                join cwd_membership ad on ad.lower_child_name = ab.lower_user_name and ad.lower_parent_name in
                                                                                       ('org-pd-frontside-h5',
                                                                                        'org-pd-frontside-native',
                                                                                        'org-pd-serverside-a',
                                                                                        'org-pd-serverside-b',
                                                                                        'org-pd-qa',
                                                                                        'org-pd-product')
                join issuestatus b on b.ID = a.issuestatus
                left join nodeassociation f on f.SOURCE_NODE_ID = a.ID and f.ASSOCIATION_TYPE = 'IssueFixVersion'
                left join projectversion i on i.ID = f.SINK_NODE_ID
                left join customfield c on c.cfname = '修正狀態'
                left join customfieldvalue d on d.CUSTOMFIELD = c.id and d.ISSUE = a.ID
                left join customfieldoption e on e.CUSTOMFIELD = c.ID and e.id = d.STRINGVALUE
                left join customfieldvalue dd on dd.CUSTOMFIELD = 10400 and dd.ISSUE = a.ID
                left join customfieldoption de on de.CUSTOMFIELD = 10400 and de.id = dd.STRINGVALUE
                left join customfield cc on cc.cfname = '開始日'
                left join customfieldvalue cd on cd.CUSTOMFIELD = cc.id and cd.ISSUE = a.ID
                left join issuelink j on j.DESTINATION = a.ID
                left join jiraissue k on k.ID = j.SOURCE
         where a.issuetype = 10003
           and a.PROJECT = 10000
           and a.issuestatus != 10001
            and a.PRIORITY<=5
          order by ad.lower_parent_name,ac.last_name,cd.DATEVALUE

這裡面就涉及到對於Jira資料庫的瞭解了,jiraissue 是issue的庫,app_user 是人員庫,cwd_membership是分組庫,customfield 、customfieldoption 、customfieldvalue 是自定義欄位的三件套。這個語句並不能直接在任何Jira系統上使用,因為裡面有較多的自定義設定規則。
比如我們增加了開始日、修正狀態兩個自定義欄位,而且定義了一種優先順序是在當前的管理面板忽略的(所以才有 PRIORITY<=5 這個條件)。

有了任務列表和人力資源列表,我們就要把兩者結合起來了。
首先我們使用的Gantt外掛是JSGantt,這個外掛還是很簡單的。我們的甘特圖是三層的,第一層是部門,第二層是人員,第三層是任務。為了在甘特圖能夠清晰的展示和任務的關係,我們迴圈所有人員的子任務,當父任務在第二部分的任務列表中時,在第一層的標題打一個星號,第二層打兩個星號,第三次打三個星號,並且任務的甘特圖顯示為紅色。除了無法拖拽修改任務排期,基本能夠滿足我們上述的需求了。這個圖也變成目前我用於檢查迭代和人員排期的最重要的工具。

歷史人員任務排期檢查

背景

所有人的任務都是在不斷滾動的,每個人每天都面臨至少四種任務:

  • 計劃性任務(長期)
  • 插入性任務(長期或短期,緊急)
  • 線上問題(重要緊急短期)
  • Bug問題(重要不緊急)

所以作為管理人員要管理好研發資源就要妥善的協調任務安排,而且時候還要針對任務變更情況進行分析,對內部人員或者外部需求單位提出改進需求。

實現

這是一個管理需求,基於人員視角,觀察一段時間區間內某個人員的任務變化情況並進行分析。
由於觀察的區間並不確定,所以我們要將每個人每天的未完成子任務快取,並且根據頁面需要獲取指定時間區間的子任務聚合。
先給出最終效果:

我們要做的事情:

  1. 定時任務用於每天定時儲存未完成子任務
  2. 後臺服務根據前端傳入條件查詢並且聚合任務資訊
  3. 前端展示

定時任務使用python作為指令碼,使用crontab凌晨執行,每次都以日期作為表名新儲存一個表,指令碼實際上使用人員任務排期管理中的sql,用 create table z_subtask_%date () 包起來就OK了。
服務端還是使用上面的python啟動的web服務,增加一個介面。
最終介面主要視角為人員,欄位包括:

  • 主任務和子任務的資訊
  • 何時建立(確認何時接收到任務)
  • 開始日期和結束日期,如果日期從未變化過則是一個日期,如果有調整則是日期區間
  • 持續天數,是指這個任務有快取在多少天的庫中,即新建之後持續了多少天
  • 最終狀態,是指到查詢截止日期這天,這個任務是否完成

這樣我們能夠基本來分析某個人員他的任務計劃性和延遲性,溝通了解原因並且制定相應的優化方案。

BI

背景

每個組織一定會有分析需求,無論是內部改進或者整體彙報,能夠有圖表和資料表的完整呈現是最基本的要求。這個需求最早是嘗試在Jira的外掛中來尋找的,但是嘗試了不少外掛發現都達不到預期的效果,最終只能轉入了使用第三方BI框架,底層資料來源自己組織。

實現

選定的就是阿里雲的QuickBI。
展示一部分效果圖:
工時維度BI報表

迭代任務分析

有了BI工具,實際上報表製作成什麼樣就沒有太大約束了。基本主要還是針對工時、迭代任務兩部分的分析。
我提供給大家兩份sql參考Jira的底層資料庫組織資料的示例:
迭代任務組織:

select concat('C_', a.id) as pissueId,
       concat("EXEPD-", a.issuenum) as ikey,
       k.pname as itype,
       a.SUMMARY,
       sum((ifnull(a.TIMESPENT, 0) + ifnull(j.TIMESPENT, 0)) / 3600) as allTimeSpent,
       sum(ifnull(ll.timeworked, 0)) / 3600 as cuMonthTimeSpent,
       b.pname,
       ifnull(ifnull((case when i.vname='hotfix' then null else i.vname end) , e.customvalue), '版本為空') as vname,
       1 as nums
from jiraissue a
       join issuestatus b on b.ID = a.issuestatus
       left join nodeassociation f on f.SOURCE_NODE_ID = a.ID and f.ASSOCIATION_TYPE = 'IssueFixVersion'
       left join projectversion i on i.ID = f.SINK_NODE_ID
       left join customfield c on c.cfname = '修正狀態'
       left join customfieldvalue d on d.CUSTOMFIELD = c.id and d.ISSUE = a.ID
       left join customfieldoption e on e.CUSTOMFIELD = c.ID and e.id = d.STRINGVALUE
       left join issuelink g on g.SOURCE = a.ID and g.LINKTYPE = 10100
       left join jiraissue j on j.ID = g.DESTINATION
       join issuetype k on k.id = a.issuetype
       left join (select l.issueid, sum(ifnull(l.timeworked, 0)) as timeworked
                  from worklog l
                  where date_format(l.STARTDATE, '%Y-%m') = '2019-06'
                  group by l.issueid) ll on ll.issueid = j.ID
where a.issuenum in (1,2,3,4)
  and a.PROJECT = 10000
group by a.ID

工時分析:

select a.id,
       a.issueid,
       concat('C_', ifnull(n.SOURCE, a.id)) as pissueId,
       a.worklogbody,
       (a.timeworked / 3600) as wmin,
       (a.timeworked / 3600 / (case d.lower_parent_name
                                 when 'org-pd-qa' then 10
                                 when 'org-pd-frontside-h5' then 9
                                 when 'org-pd-frontside-native' then 5
                                 when 'org-pd-serverside-b' then 12
                                 when 'org-pd-serverside-a' then 7
                                 when 'org-pd-product' then 9
                                 else '' end)) as wminperm,
       date_format(a.STARTDATE, '%Y-%m-%d') as sdate,
       date_format(a.STARTDATE, '%Y-%m') as sdm,
       c.last_name,
       (case d.lower_parent_name
          when 'org-pd-qa' then '測試'
          when 'org-pd-frontside-h5' then '前端h5'
          when 'org-pd-frontside-native' then '原生'
          when 'org-pd-serverside-b' then '業務'
          when 'org-pd-serverside-a' then '架構'
          when 'org-pd-product' then '產品UI'
          else '' end) as deptname,
       k.pname,
       concat(h.pkey, '-', e.issuenum) as issuekey,
       concat('https://jira.exexm.com/browse/', h.pkey, '-', e.issuenum) as issueurl,
       (case j.pname when 'Sub-task' then '任務與子任務' when 'Task' then '任務與子任務' else j.pname end) as issuetypename,
       -- (case j.pname when 'Sub-task' then '子任務' when 'Task' then '任務' else j.pname end) as issuetypename,
       e.SUMMARY,
       -- ifnull(ifnull(i.vname, ifnull(m.customvalue,mm.customvalue)), '版本為空') as vname,
       i.vname,
       (case
          when
            (i.vname is null and (l.STRINGVALUE is not null or ll.STRINGVALUE is not null))
            then 'hotfix'
          when
            (i.vname is null or i.vname='')
            then '版本為空'
          when
            a.issueid = 10752
            then '日常事務'
          when
            a.issueid = 18019
            then '規劃外事務'
          else i.vname end) as cord
from worklog a
       join app_user b on b.user_key = a.AUTHOR
       join cwd_user c on c.lower_user_name = b.lower_user_name
       join cwd_membership d on d.lower_child_name = b.lower_user_name and d.lower_parent_name in
                                                                           ('org-pd-frontside-h5',
                                                                            'org-pd-frontside-native',
                                                                            'org-pd-serverside-a',
                                                                            'org-pd-serverside-b',
                                                                            'org-pd-qa',
                                                                            'org-pd-product')
       join jiraissue e on e.ID = a.issueid
       left join nodeassociation f on f.SOURCE_NODE_ID = e.ID and f.ASSOCIATION_TYPE = 'IssueFixVersion'
       left join projectversion i on i.ID = f.SINK_NODE_ID
       join issuetype j on j.id = e.issuetype
       join project h on h.ID = e.PROJECT
       left join issuelink n on n.DESTINATION = e.ID and n.LINKTYPE = 10100
       join issuestatus k on k.ID = e.issuestatus
       left join customfieldvalue l on l.CUSTOMFIELD = 10025 and l.ISSUE = n.SOURCE
       left join customfieldoption m on m.CUSTOMFIELD = 10025 and m.id = l.STRINGVALUE
       left join customfieldvalue ll on ll.CUSTOMFIELD = 10025 and ll.ISSUE = e.ID
       left join customfieldoption mm on mm.CUSTOMFIELD = 10025 and mm.id = ll.STRINGVALUE
where a.STARTDATE >= '2019-1-1'
order by a.STARTDATE asc

總結

寫到這裡,所有關於Jira的使用的整理目前告一段落了。但是這應該還只是一個階段性里程碑而已,我們的組織架構在進步,過程管理方法在進步,Atlassian也在進步,所以我們也不能停下自己的腳步。Jira還只是完整生態當中的一個環節,我們要更好的組織我們的研發團隊,後面還有一些夥伴給大家介紹。一個企業、研發團隊要能長久的生存和提高,就需要積累,但是不能口口相傳,還要能進行階段性的梳理和提高。下一章就是企業如何使用Confluence進行知識積累