<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實(shí)戰(zhàn) | 如何用 Python 統(tǒng)計(jì) Jira 數(shù)據(jù)并可視化

          共 17891字,需瀏覽 36分鐘

           ·

          2022-07-26 23:24

          大家好,我是安果!

          目前公司使用 Jira 作為項(xiàng)目管理工具,在每一次迭代完成后的復(fù)盤(pán)會(huì)上,我們都需要針對(duì)本次迭代的 Bug 進(jìn)行數(shù)據(jù)統(tǒng)計(jì),以幫助管理層能更直觀(guān)的了解研發(fā)的代碼質(zhì)量

          本篇文章將介紹如何利用統(tǒng)計(jì) Jira 數(shù)據(jù),并進(jìn)行可視化

          1. 準(zhǔn)備

          首先,安裝 Python 依賴(lài)庫(kù)

          # 安裝依賴(lài)庫(kù)
          pip3 install jira
          pip3 install html-table
          pip3 install pyecharts
          pip3 install snapshot_selenium

          其中

          • jira 使用 jsql 語(yǔ)法從在項(xiàng)目中獲取需要的數(shù)據(jù)

          • html-table 用于生成一個(gè) HTML 格式的表格數(shù)據(jù)

          • pyecharts 和 snapshot_selenium 用于數(shù)據(jù)可視化

          2. 實(shí)戰(zhàn)一下

          下面我們通過(guò) 7 個(gè)步驟來(lái)實(shí)現(xiàn)上面的功能

          2-1 登錄獲取客戶(hù)端連接對(duì)象

          from jira import JIRA

          class JiraObj(object):
              def __init__(self, bug_style, project_type):
                  """
                  :param project_name
                  :param sprint: 迭代號(hào)碼
                  :param bug_style: BUG狀態(tài)
                  """

                  # Jira首頁(yè)地址
                  self.server = 'https://jira.**.team'

                  # Jira登錄賬號(hào)信息
                  self.basic_auth = ('用戶(hù)名''密碼')

                  # 創(chuàng)建一個(gè)客戶(hù)端連接信息
                  self.jiraClinet = JIRA(server=self.server, basic_auth=self.basic_auth)

          2-2 根據(jù)項(xiàng)目類(lèi)型獲取看板 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
          ..

          2-3 根據(jù)看板 id 獲取迭代 id 及迭代名稱(chēng)

          ...
           # 獲取項(xiàng)目Sprint,讓用戶(hù)進(jìn)行選擇
                  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
          ...

          2-4  根據(jù)項(xiàng)目名、Bug 類(lèi)型、迭代 id 組成 jsql 語(yǔ)句,并查詢(xún)數(shù)據(jù)

          ...
           def get_bug_status_jsql(self, bug_status: BUG_STATUS):
                  """
                  通過(guò)bug狀態(tài),獲取jsql
                  :param bug_status:
                  :return:
                  """

                  status_jsql = ''
                  if bug_status == BUG_STATUS.ALL:
                      status_jsql = ' '
                  elif bug_status == BUG_STATUS.TO_VERIFY:
                      # 待驗(yàn)證(已解決)
                      status_jsql = ' AND status = 已解決 '
                  elif bug_status == BUG_STATUS.TO_FIXED:
                      # 待解決(打開(kāi)、重新打開(kāi)、處理中)
                      status_jsql = ' AND status in (打開(kāi), 重新打開(kāi), 處理中) '
                  elif bug_status == BUG_STATUS.CLOSED:
                      # 關(guān)閉
                      status_jsql = ' AND status = Closed '
                  elif bug_status == BUG_STATUS.TO_FIXED_CONTAIN_DELAY:
                      # 待解決(打開(kāi)、重新打開(kāi)、處理中、延期處理)
                      status_jsql = ' AND status in (打開(kāi), 延期處理, 重新打開(kāi), 處理中) '
                  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)
          ...

          2-5  生成本地 HTML 統(tǒng)計(jì)數(shù)據(jù)

          需要注意的是,使用 a 標(biāo)簽組裝的鏈接不能直接跳轉(zhuǎn),需要針對(duì)數(shù)據(jù)進(jìn)行二次替換才能正常進(jìn)行鏈接跳轉(zhuǎn) 

          from HTMLTable import (
              HTMLTable
          )

          ...
           def gen_html_table(self, datas):
                  """
                  初始化表單樣式
                  :return:
                  """

                  table = HTMLTable(caption=f'實(shí)時(shí)BUG統(tǒng)計(jì)【{self.project_name}】,一共{len(datas)}個(gè)')

                  # 表頭行
                  table.append_header_rows((('ID''狀態(tài)''優(yōu)先級(jí)''責(zé)任人''終端''URL'),))

                  # 添加數(shù)據(jù)
                  table.append_data_rows(datas)

                  # 設(shè)置樣式
                  table.caption.set_style({'font-size''15px'})

                  # 其他樣式設(shè)置
                  ...

                  # 替換數(shù)據(jù),便于展示href地址
                  html = table.to_html().replace("&lt;""<").replace("&gt;"">").replace("&quot;"'"')

                  with open(f"./output/{self.project_name}-bug_{current_time()}.html"'w', encoding='utf-8'as file:
                      file.write(html)
          ...
          # 生成本地文件的數(shù)據(jù)
          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">點(diǎn)我查看</a>'for item in lists])

          # 生成本地HTML文件
          self.gen_html_table(output_tuples)
          ..

          2-6 數(shù)據(jù)統(tǒng)計(jì)

          首先,這里按 Bug 責(zé)任人進(jìn)行分組,然后按數(shù)目進(jìn)行降序排列

          然后,按 Bug 優(yōu)先等級(jí)進(jìn)行降序排列

          最后,獲取每一個(gè)端的 Bug 總數(shù)

          ...
                  # 2、統(tǒng)計(jì)每個(gè)人(按數(shù)目)
                  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總數(shù)排序:", datas_by_count)

                  # 3、統(tǒng)計(jì)每個(gè)人(按優(yōu)先級(jí))
                  datas_by_priority = {}

                  for item in datas_by_count:
                      # 責(zé)任人
                      name = item[0]
                      # 5個(gè)優(yōu)先級(jí)對(duì)應(yīng)的數(shù)目
                      counts = self.get_assignee_count(lists, name)
                      datas_by_priority[name] = counts

                  # 排序(按優(yōu)先級(jí)多條件降序排列)
                  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優(yōu)先級(jí)排序:", datas_by_priority)

                  # 4、根據(jù)終端進(jìn)行統(tǒng)計(jì)分類(lèi)
                  keys, values = self.get_end_type_count(lists)
          ...

          2-7 可視化

          針對(duì)上面的 3 組數(shù)據(jù),使用 pyecharts 繪制成柱狀圖和餅狀圖

          ...
                def draw_image(self, datas_by_count, datas_by_priority, keys, values):
                  """
                  繪制圖片
                  :param values:
                  :param keys:
                  :param datas_by_count: 按bug總數(shù)排序結(jié)果
                  :param datas_by_priority: 按bug優(yōu)先級(jí)排序結(jié)果
                  :return:
                  """

                  # 1、按BUG總數(shù)排序繪制
                  bar = (
                      Bar().set_global_opts(
                          title_opts=opts.TitleOpts(title=f"{self.project_name}", subtitle=f"{self.sprint_name}")))
                  bar.add_xaxis([item[0for item in datas_by_count])
                  bar.add_yaxis(f"BUG總數(shù)", [item[1for item in datas_by_count])

                  # render 會(huì)生成本地 HTML 文件,默認(rèn)會(huì)在當(dāng)前目錄生成 render.html 文件
                  # 也可以傳入路徑參數(shù),如 bar.render("mycharts.html")
                  # bar.render(path=f'{sprint_name}-BUG總數(shù).html')
                  make_snapshot(snapshot, bar.render(), "./output/1.png")

                  # 2、按優(yōu)先級(jí)排序繪制
                  bar2 = (
                      # Bar(init_opts=opts.InitOpts(theme=ThemeType.INFOGRAPHIC))
                      Bar()
                          .add_xaxis([item[0for item in datas_by_priority])
                          .add_yaxis(self.__get_priority(BUG_PRIORITY.Highest), [item[1][0for item in datas_by_priority],
                                     color='#6aa84f')
                          .add_yaxis(self.__get_priority(BUG_PRIORITY.High), [item[1][1for item in datas_by_priority],
                                     color='#a2c4c9')
                          .add_yaxis(self.__get_priority(BUG_PRIORITY.Medium), [item[1][2for item in datas_by_priority],
                                     color="#ff9900")
                          .add_yaxis(self.__get_priority(BUG_PRIORITY.Low), [item[1][3for item in datas_by_priority],
                                     color="#ea9999")
                          .add_yaxis(self.__get_priority(BUG_PRIORITY.Lowest), [item[1][4for 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優(yōu)先級(jí).html')
                  make_snapshot(snapshot, bar2.render(), "./output/2.png")

                  # 3、根據(jù)終端來(lái)繪制餅圖
                  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=": {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)
          ...

          3. 總結(jié)

          通過(guò)上面的操作,每次只需要輸入項(xiàng)目類(lèi)型、迭代版本號(hào)、要統(tǒng)計(jì)的 Bug 類(lèi)型,就能統(tǒng)計(jì)出所需要的數(shù)據(jù)并繪制成圖表

          我已經(jīng)將文中所有源碼上傳到后臺(tái),回復(fù)關(guān)鍵字「 jira 」獲取完整的源碼

          如果你覺(jué)得文章還不錯(cuò),請(qǐng)大家 點(diǎn)贊、分享、留言 下,因?yàn)檫@將是我持續(xù)輸出更多優(yōu)質(zhì)文章的最強(qiáng)動(dòng)力!


          推薦閱讀


          自動(dòng)化篇 | 實(shí)現(xiàn)自動(dòng)化搶茅臺(tái)超詳細(xì)過(guò)程!

          5 分鐘,教你從零快速編寫(xiě)一個(gè)油猴腳本!

          5 分鐘,教你用 Docker 部署一個(gè) Python 應(yīng)用!

          最全總結(jié) | 聊聊 Python 命令行參數(shù)化的幾種方式!

          實(shí)戰(zhàn) | 如何用 Python 自動(dòng)化監(jiān)控文件夾完成服務(wù)部署!


          END


          好文和朋友一起看~
          瀏覽 101
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久深沈爱 | 九九成人电影www | 韩国乱伦电影翔田千里 | 精品乱伦小说 | 亚洲无码五月天 |