实战教程:Python统计并分析Jira数据实现可视化


实战 | 如何用 Python 统计 Jira 数据并可视化_数据可视化

大家好,我是安果!

目前公司使用 Jira 作为项目管理工具,在每一次迭代完成后的复盘会上,我们都需要针对本次迭代的 Bug 进行数据统计,以帮助管理层能更直观的了解研发的代码质量

本篇文章将介绍如何利用统计 Jira 数据,并进行可视化

1. 准备

首先,安装 Python 依赖库

# 安装依赖库
pip3 install jira
pip3 install html-table
pip3 install pyecharts
pip3 install snapshot_selenium
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

其中

  • jira 使用 jsql 语法从在项目中获取需要的数据
  • html-table 用于生成一个 HTML 格式的表格数据
  • pyecharts 和 snapshot_selenium 用于数据可视化

2. 实战一下

下面我们通过 7 个步骤来实现上面的功能

2-1 登录获取客户端连接对象

from jira import JIRA

class JiraObj(object):
def __init__(self, bug_style, project_type):
"""
:param project_name
:param sprint: 迭代号码
:param bug_style: BUG状态
"""
# Jira首页地址
self.server = 'https://jira.**.team'

# Jira登录账号信息
self.basic_auth = ('用户名', '密码')

# 创建一个客户端连接信息
self.jiraClinet = JIRA(server=self.server, basic_auth=self.basic_auth)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

2-2 根据项目类型获取看板 id

...
# 获取boards看板
# 所有看板信息
boards = [(item.id, item.name) for item in self.jiraClinet.boards()]
board_id = self.__get_board_id(boards, project_type)
print("看板id:", board_id)
...
def __get_board_id(self, boards, project_type):
"""
获取看板id
:param project_type:
:return:
"""
board_id = 1
for item in boards:
if (project_type == PROJ_TYPE.Type1 and item[1] == 't1') or (
project_type == PROJ_TYPE.Type2 and item[1] == 't2'):
board_id = item[0]
break
return board_id
..
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

2-3 根据看板 id 获取迭代 id 及迭代名称

...
# 获取项目Sprint,让用户进行选择
sprints = self.jiraClinet.sprints(board_id=board_id)
for item in sprints:
if str(sprint_no) in item.name:
self.sprint_id = item.id
self.sprint_name = item.name
print(f"选择Sprint,id:{self.sprint_id},name:{self.sprint_name}")
break
...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

2-4  根据项目名、Bug 类型、迭代 id 组成 jsql 语句,并查询数据

...
def get_bug_status_jsql(self, bug_status: BUG_STATUS):
"""
通过bug状态,获取jsql
:param bug_status:
:return:
"""
status_jsql = ''
if bug_status == BUG_STATUS.ALL:
status_jsql = ' '
elif bug_status == BUG_STATUS.TO_VERIFY:
# 待验证(已解决)
status_jsql = ' AND status = 已解决 '
elif bug_status == BUG_STATUS.TO_FIXED:
# 待解决(打开、重新打开、处理中)
status_jsql = ' AND status in (打开, 重新打开, 处理中) '
elif bug_status == BUG_STATUS.CLOSED:
# 关闭
status_jsql = ' AND status = Closed '
elif bug_status == BUG_STATUS.TO_FIXED_CONTAIN_DELAY:
# 待解决(打开、重新打开、处理中、延期处理)
status_jsql = ' AND status in (打开, 延期处理, 重新打开, 处理中) '
return status_jsql
...
jql = f'project = {project_name} and issuetype = 故障 {self.get_bug_status_jsql(self.bug_style)} AND Sprint = {self.sprint_id} ORDER BY priority desc, updated DESC'
print(jql)
lists = self.get_issue_list(jql)
...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

2-5  生成本地 HTML 统计数据

需要注意的是,使用 a 标签组装的链接不能直接跳转,需要针对数据进行二次替换才能正常进行链接跳转 

from HTMLTable import (
HTMLTable
)

...
def gen_html_table(self, datas):
"""
初始化表单样式
:return:
"""
table = HTMLTable(caption=f'实时BUG统计【{self.project_name}】,一共{len(datas)}个')

# 表头行
table.append_header_rows((('ID', '状态', '优先级', '责任人', '终端', 'URL'),))

# 添加数据
table.append_data_rows(datas)

# 设置样式
table.caption.set_style({'font-size': '15px'})

# 其他样式设置
...

# 替换数据,便于展示href地址
html = table.to_html().replace("<", "<").replace(">", ">").replace(""", '"')

with open(f"./output/{self.project_name}-bug_{current_time()}.html", 'w', encoding='utf-8') as file:
file.write(html)
...
# 生成本地文件的数据
output_tuples = tuple([
(item.get("key"), item.get("status"), item.get("priority"), item.get('duty'), item.get('end_type'),
f'<a href="{item.get("url")}" target="_blank">点我查看</a>') for item in lists])

# 生成本地HTML文件
self.gen_html_table(output_tuples)
..
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

2-6 数据统计

首先,这里按 Bug 责任人进行分组,然后按数目进行降序排列

然后,按 Bug 优先等级进行降序排列

最后,获取每一个端的 Bug 总数

...
# 2、统计每个人(按数目)
datas_by_count = {}
for item in lists:
datas_by_count[item.get("duty")] = datas_by_count.get(item.get("duty"), 0) + 1

# 降序排序
datas_by_count = sorted(datas_by_count.items(), key=lambda item: item[1], reverse=True)

# print("按Bug总数排序:", datas_by_count)

# 3、统计每个人(按优先级)
datas_by_priority = {}

for item in datas_by_count:
# 责任人
name = item[0]
# 5个优先级对应的数目
counts = self.get_assignee_count(lists, name)
datas_by_priority[name] = counts

# 排序(按优先级多条件降序排列)
datas_by_priority = sorted(datas_by_priority.items(),
key=lambda item: (item[1][0], item[1][1], item[1][2], item[1][3]), reverse=True)

# print("按Bug优先级排序:", datas_by_priority)

# 4、根据终端进行统计分类
keys, values = self.get_end_type_count(lists)
...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

2-7 可视化

针对上面的 3 组数据,使用 pyecharts 绘制成柱状图和饼状图

...
def draw_image(self, datas_by_count, datas_by_priority, keys, values):
"""
绘制图片
:param values:
:param keys:
:param datas_by_count: 按bug总数排序结果
:param datas_by_priority: 按bug优先级排序结果
:return:
"""
# 1、按BUG总数排序绘制
bar = (
Bar().set_global_opts(
title_opts=opts.TitleOpts(title=f"{self.project_name}", subtitle=f"{self.sprint_name}")))
bar.add_xaxis([item[0] for item in datas_by_count])
bar.add_yaxis(f"BUG总数", [item[1] for item in datas_by_count])

# render 会生成本地 HTML 文件,默认会在当前目录生成 render.html 文件
# 也可以传入路径参数,如 bar.render("mycharts.html")
# bar.render(path=f'{sprint_name}-BUG总数.html')
make_snapshot(snapshot, bar.render(), "./output/1.png")

# 2、按优先级排序绘制
bar2 = (
# Bar(init_opts=opts.InitOpts(theme=ThemeType.INFOGRAPHIC))
Bar()
.add_xaxis([item[0] for item in datas_by_priority])
.add_yaxis(self.__get_priority(BUG_PRIORITY.Highest), [item[1][0] for item in datas_by_priority],
color='#6aa84f')
.add_yaxis(self.__get_priority(BUG_PRIORITY.High), [item[1][1] for item in datas_by_priority],
color='#a2c4c9')
.add_yaxis(self.__get_priority(BUG_PRIORITY.Medium), [item[1][2] for item in datas_by_priority],
color="#ff9900")
.add_yaxis(self.__get_priority(BUG_PRIORITY.Low), [item[1][3] for item in datas_by_priority],
color="#ea9999")
.add_yaxis(self.__get_priority(BUG_PRIORITY.Lowest), [item[1][4] for item in datas_by_priority],
color="#980000")
.set_global_opts(
title_opts=opts.TitleOpts(title=f"{self.project_name}", subtitle=f"{self.sprint_name}"))
)
# bar2.render(path=f'{sprint_name}-BUG优先级.html')
make_snapshot(snapshot, bar2.render(), "./output/2.png")

# 3、根据终端来绘制饼图
if len(keys) > 0 and len(values) > 0:
c = (
Pie()
.add("", [list(z) for z in zip(keys, values)])
.set_global_opts(title_opts=opts.TitleOpts(title="各端BUG分布"))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
)
make_snapshot(snapshot, c.render(), f"./output/{self.project_name}_end.png")

# 4、合并两张图片
self.concatenate_img(['./output/1.png', './output/2.png'], img_name=f'./output/{self.sprint_name}_bug.png',
axis=1)
...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

3. 总结

通过上面的操作,每次只需要输入项目类型、迭代版本号、要统计的 Bug 类型,就能统计出所需要的数据并绘制成图表

我已经将文中所有源码上传到后台,回复关键字「 jira 」获取完整的源码

如果你觉得文章还不错,请大家 点赞、分享、留言 下,因为这将是我持续输出更多优质文章的最强动力!


QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空