Atlassian In Action-Jira之二次开发(五)

到现在已经写到了第五章节,实际上离Jira的官方系统已经越来越远,本章节的内容基本上已经完全脱离了Jira这个系统本身,而是依赖Jira的API接口和数据库进行开发了。主要包含如下几个功能:

  • 人员任务排期管理

  • 历史人员任务排期检查

  • BI报表

注意

由于我们的二次开发基本都是做成静态页面,但是大量使用了Jira的API接口,为了能够方便的使用。所以我们将这些页面放到Jira的容器当中,在其中建立了一个目录。这样就能够和Jira在同一个域名下,只要Jira系统有登录,访问接口都是不需要额外进行认证的。

人员任务排期管理

背景

这个页面的需求实际上是源自于BigGantt这个插件,我们前面讲过这是一个非常强大的插件,对于迭代的任务管理有很大的帮助。

但是在慢慢的使用过程中产生了一个管理的场景:有一个重点项目占用了一部分的研发力量,需要了解这部分的任务安排情况。并且当任务有变更的时候,能够快速了解是否还有研发资源可以安排调整。

分析之后我们了解到,实际上这个需求有两个重点,一要有一个任务列表(通常是一个JQL语句筛选出的主任务列表),二要有涉及的人力资源和子任务清单。BigGantt是否满足呢?它可以根据JQL筛选出任务列表,但是这个列表只能够以任务为视角展开。当我们需要评估某个人员排期如何,是否能够插入或者调整时,没有太好的界面来清晰的展示。

实现

所以我们设计了如下的功能:

人员任务排期管理

可以看到这个界面分为三部分

第一部分是文本输入框,用于执行JQL语句。

第二部分是主任务列表。

第三部分是人力资源甘特图。

任务列表通过


<pre id="line1" style="margin: 8px 0px;">https://jira.yourdomain.com/rest/api/2/search?jql=' + jqlstr + '&maxResults=9999</pre>

接口就可以获取任务的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报表

迭代任务分析

迭代任务分析

有了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进行知识积累。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容