天天看點

禅道bug統計并發送釘釘通知

一、背景

通過定期輸出每條産品的 BUG 情況,以此來回報開發解決問題、測試跟進問題的情況;釘釘群推送提醒開發及時解決

以此我這邊開始着手準備編寫一個小工具,最終達到目的:自動定期發送統計報告,報告次元(資料 + html展示)

二、技術選型

python + markdown + pymysql + html + jenkins + 釘釘機器人

三、實作思路

python主要用到sshtunnel,pysql庫跳闆機連接配接mysql資料庫,yaml記錄項目配置參數,request調取釘釘接口

1、讀取禅道資料庫資料及狀态,封裝sql類,把各次元統計資料通過str格式傳回

2、禅道bug彙總資料進行進行拼接,生成模闆寫入markdown檔案。(釘釘會支援簡單markdown格式,表格等等不支援)

3、禅道bug明細資料,生成html頁面(沒找到合适的三方庫隻能手動撸)

4、調取釘釘自定義接口,進行資料請求。

5、jenkins實作定期推送+html頁面展示

四、功能代碼

1、禅道SQL統計代碼

import datetime

import pymysql


class MysqlDB(object):
    def __init__(self, host, port, user, passwd, db):
        self.conn = pymysql.connect(host=host,
                                    port=port,
                                    user=user,
                                    passwd=passwd,  # passwd 不是 password
                                    db=db)
        self.cur = self.conn.cursor()

    def __del__(self):  # 析構函數,執行個體删除時觸發
        self.cur.close()
        self.conn.close()

    def bug_count(self, day, product):
        """
        最近總的BUG情況統計明細資料
        :param: day 根據輸入天數
        :param: product 産品号
        :return:
        """
        now = (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S')  # 伺服器時區要 + 8h
        now_day = datetime.datetime.now().strftime('%Y-%m-%d')
        recent_sevenday = (datetime.datetime.now() - datetime.timedelta(days=day)).strftime("%Y-%m-%d %H:%M:%S")
        bug_sql = """SELECT p.name,b.title,b.assignedTo,b.severity,b.type,b.status,b.openedBy, CAST(openedDate AS CHAR) AS openedDate from zt_bug b left join zt_product p on b.product = p.id
    where b.STATUS <> 'closed' and b.product in (%s) and openedDate between "%s" and "%s";""" % (
            product, recent_sevenday, now)
        print(bug_sql)
        recent_sevenday_bug = self.query(bug_sql)
        return recent_sevenday_bug

    # 查詢低代碼bug彙總
    def bug_total(self, day, product):
        """
        最近總的BUG情況統計統計
        :param: day 根據輸入天數
        :param: product 産品号
        :return:
        """
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(now)
        recent_sevenday = (datetime.datetime.now() - datetime.timedelta(days=day)).strftime('%Y-%m-%d')
        print(recent_sevenday)
        # 新增bug數
        new_near_bug_sql = """SELECT COUNT(*) as new_near_bug from zt_bug where product = "%s"  and openedDate between "%s" and "%s";""" % (
            product, recent_sevenday, now)
        self.cur.execute(new_near_bug_sql)
        new_near_bug = self.cur.fetchone()

        # 已解決bug數
        # close_bug_sql = """SELECT COUNT(*) as close_bug from zt_bug where product = 39 and status = "closed" and openedDate between "%s" and "%s";""" % (
        #     recent_sevenday, now)
        # self.cur.execute(close_bug_sql)
        # close_bug = self.cur.fetchone()

        # 未解決bug數
        open_bug_sql = """SELECT COUNT(*) as open_bug from zt_bug where product = "%s"  and  status = "active" and openedDate between "%s" and "%s";""" % (
            product, recent_sevenday, now)
        self.cur.execute(open_bug_sql)
        open_bug = self.cur.fetchone()
        print("dayi", open_bug_sql)

        # 未解決嚴重程度1級或優先級1級的bug
        open_bug_sql1 = """SELECT COUNT(*) as open_bug from zt_bug where product = "%s"  and  status = "active" and openedDate between "%s" and "%s" and  (severity=1 or pri=1);""" % (
            product, recent_sevenday, now)
        self.cur.execute(open_bug_sql1)
        open_bug1 = self.cur.fetchone()

        # 未解決嚴重程度2級或優先級2級的bug
        open_bug_sql2 = """SELECT COUNT(*) as open_bug from zt_bug where product = "%s"  and  status = "active" and openedDate between "%s" and "%s" and  ((severity=2 and pri>=2) or (severity>=2 and pri=2));""" % (
            product, recent_sevenday, now)
        self.cur.execute(open_bug_sql2)
        open_bug2 = self.cur.fetchone()
        print("11111", open_bug2)

        # 未解決嚴重程度3級或優先級3級的bug
        open_bug_sql3 = """SELECT COUNT(*) as open_bug from zt_bug where product = "%s"  and  status = "active" and openedDate between "%s" and "%s" and  ((severity=3 and pri>=3) or (severity>=3 and pri=3));""" % (
            product, recent_sevenday, now)
        self.cur.execute(open_bug_sql3)
        open_bug3 = self.cur.fetchone()
        print("222", open_bug3)

        # 未解決嚴重程度4級或優先級4級的bug
        open_bug_sql4 = """SELECT COUNT(*) as open_bug from zt_bug where product = "%s"  and  status = "active" and openedDate between "%s" and "%s" and  (severity=4 and pri=4);""" % (
            product, recent_sevenday, now)
        self.cur.execute(open_bug_sql4)
        open_bug4 = self.cur.fetchone()

        # 已解決待驗證bug數
        close_unbug_sql = """SELECT COUNT(*) as close_unbug from zt_bug where product = "%s"  and  status = "resolved" and openedDate between "%s" and "%s";""" % (
            product, recent_sevenday, now)
        self.cur.execute(close_unbug_sql)
        close_unbug = self.cur.fetchone()

        # 提BUG、指派、轉派、改BUG
        statistics_bug = "新增BUG數:{0} \n\n 未解決BUG數:{1} \n\n 未解決嚴重程度1級或優先級1級bug:{2} \n\n未解決嚴重程度2級或優先級2級bug:{3} \n\n未解決嚴重程度3級或優先級3級bug:{4} \n\n未解決嚴重程度4級或優先級4級bug:{5} \n\n 已解決待驗證BUG數:{6}".format(
            new_near_bug[0],
            open_bug[0], open_bug1[0], open_bug2[0], open_bug3[0], open_bug4[0],
            close_unbug[0])
        print(statistics_bug)
        print(type(statistics_bug))
        return statistics_bug

    # 查詢bug明細
    def bug_detail(self, day, product):
        """
        最近總的BUG情況統計明細資料
        :param: day 根據輸入天數
        :param: product 産品号
        :return:
        """
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        recent_sevenday = (datetime.datetime.now() - datetime.timedelta(days=day)).strftime('%Y-%m-%d')
        self.cur.execute(
            """select "%s" as 開始時間 ,"%s" as 結束時間,u.realname as 姓名 ,count(*) as 總bug數, 
            sum(case when b.status="active" then 1 else 0  end) as 未解決bug數,
            sum(case when b.status="active" and (b.severity=1 or b.pri=1) then 1 else 0 end) as 未解決1級bug,
            sum(case when b.status="active" and ((severity=2 and pri>=2) or (severity>=2 and pri=2)) then 1 else 0 end) as 未解決2級bug,
            sum(case when b.status="active" and ((severity=3 and pri>=3) or (severity>=3 and pri=3)) then 1 else 0 end) as 未解決3級bug,
            sum(case when b.status="active" and (b.severity=4 and b.pri=4) then 1 else 0 end) as 未解決4級bug,
            sum(case when b.status="resolved" then 1 else 0 end) as 已解決待驗證bug數 
            from zt_bug b right join zt_user u on b.assignedTo = u.account 
            where b.status and b.openedDate BETWEEN "%s" and "%s" and b.product = "%s" 
            group by b.assignedTo order by 總bug數 desc;""" % (
                recent_sevenday, now, recent_sevenday, now, product))
        return self.cur      

2、配置檔案代碼(讀取配置的代碼可以參考我之前寫的config.py的檔案)

zentao_mysql:
  host: 禅道連結位址
  port: 3306
  user: 使用者名
  passwd: 密碼
  db: zentao


zentao_data:
   # 我上傳的檔案伺服器位址,如果需要統一域名下的話,可以起個服務再輸入需要通路機器的ipv4位址
  server: "http://xxxxxxxxxxx:63332/"
  #釘釘機器人的webhook
  url1: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxxx"
  sign_mark: "本周"
  markdown:
    title: "測試bug統計"
  is_at_all: True # @全體成員(在此可設定@特定某人)      

3、釘釘接口推送以及html界面繪制

import datetime

from dingtalkchatbot.chatbot import DingtalkChatbot, FeedLink

from utils.config import Config


class ding_talk():
    def send_bug(self, data_file):
        data = Config("/Users/leiyuxing/PycharmProjects/project/ZYL/bug_tj/db_config.yaml").get('zentao_data')
        title = data.get('markdown').get('title')
        server = data.get('server')
        is_at_all = data.get('is_at_all')
        url = data.get('url')
        sign_mark = data.get('sign_mark')
        xiaoding = DingtalkChatbot(url)
        # Markdown消息@所有人
        now = datetime.datetime.now().strftime("%Y%m%d%H%M")
        now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        xiaoding.send_markdown(title=title,
                               text='#### **{0}禅道BUG情況統計**\n\n 各位同學,以下是截止到{1}的禅道BUG情況統計,詳情頁為個人Bug彙總明細,如有自己未解決的BUG資料,請盡快處理!\n\n {2} \n\n[檢視詳情]({3}bug_tj/bug_detail_{4}.html) \n'.format(
                                   sign_mark, now_time, data_file, server, now), is_at_all=is_at_all)

    def bug_html(self, lis, html_file):
        """
        對查詢bug明細轉html檔案
        :param lis
        :param html_file
        """
        conten_title = []
        for key in lis.description:
            conten_title.append(key[0])
        a = "</th><th>".join(conten_title)
        con_title = "<tr><th>" + a + "</th></tr>"
        conten_val = ""
        con = ""
        lis_arr = lis.fetchall()
        for i in range(0, len(lis_arr)):
            for index, v in enumerate(lis_arr[i]):
                if index == 0:
                    conten_val = "<tr><td>" + lis_arr[i][index] + "</td><td>"
                    con = con + conten_val;
                    continue
                con = con + str(lis_arr[i][index]) + "</td><td>"
            con = con[0:-2] + "r>"
            con = con + "\n"
        head = """<meta charset="utf-8">
        <style type="text/css">
        table.tftable {font-size:12px;color:#333333;width:100%;border-width: 1px;border-color: #9dcc7a;border-collapse: collapse;}
        table.tftable th {font-size:12px;background-color:#abd28e;border-width: 1px;padding: 8px;border-style: solid;border-color: #9dcc7a;text-align:left;}
        table.tftable tr {background-color:#ffffff;}
        table.tftable td {font-size:12px;border-width: 1px;padding: 8px;border-style: solid;border-color: #9dcc7a;}
        </style>\n<table id="tfhover" class="tftable" border="1">\n"""
        remarks = "<tr><td colspan=10><font  size=4 color=red >備注說明:</font><font size=2 color=red ><br><br>1、總bug數 = 未解決bug數 + 已解決待驗證bug數<br>2、未解決bug數 = 未解決1級bug + 未解決2級bug + 未解決3級bug + 未解決4級bug<br>3、以上bug統計包含前幾個版本遺留的問題且在目前版本也需要處理的統計<br>4、以上人員排序按照總bug數倒序排序</br></font></td><tr>"
        last = "</table>"
        htm = head + con_title + con + remarks + last
        with open(html_file, "w", encoding="utf-8") as f:
            f.write(htm)      

4、調用函數

import requests
import datetime

from bug_tj.dingtalk import ding_talk
from bug_tj.mysql import MysqlDB
from utils.config import Config

ding_ding = ding_talk()
mysql = Config("/Users/leiyuxing/PycharmProjects/project/ZYL/bug_tj/db_config.yaml").get('zentao_mysql')
HOST = mysql.get('host')
PORT = mysql.get('port')
USER = mysql.get('user')
PASSWD = mysql.get('passwd')
DB = mysql.get('db')

db = MysqlDB(HOST, PORT, USER, PASSWD, DB)
cursor_execute = db.bug_detail(30, 39)
print(cursor_execute)

today_ymd = datetime.datetime.now().strftime("%Y%m%d%H%M")
path = "/Users/leiyuxing/PycharmProjects/project/ZYL/bug_tj/bug_detail_{0}.html".format(today_ymd)

ding_ding.bug_html(cursor_execute, path)
seven_count = db.bug_total(30, 39)
print(seven_count)
ding_ding.send_bug(seven_count)

# 上傳檔案伺服器位址(這段是上傳寶塔伺服器的代碼)
url = "http://xxxxxxx:63333/file_upload"
files = {'file': open(path, 'rb')}
r = requests.post(url, files=files)      

5、展示效果:

釘釘通知

禅道bug統計并發送釘釘通知

檢視詳情:

禅道bug統計并發送釘釘通知

五、業務統計規則說明 

新增,新增日期為曆史遺留bug截止到本周内的bug

已解決,解決日期為本周内的。被開發設定為已解決的。其中可能有部分是上個版本或上周遺留下來的,展現了開發在本周的變化情況(包括設計如此、重複 BUG、外部原因、無法重制、不予解決、轉為需求),不包含延期處理

已關閉,關閉日期為截止到本周内的bug。是測試驗證過,确實已經解決的,包括其中有的是上幾個版本或上周遺留下來的

未解決,目前顯示 BUG 狀态為未解決的。包含目前還沒被解決的、之前遺留的未解決、以及 reopen 的 BUG(累計資料)

未解決一級bug,截止到目前嚴重程度一級或優先級一級的bug(二、三、四級同理),大概如下一個規則:

一級:   (1,2),(1,1),(1,3),(1,4)(2,1),(3,1),(4,1). s=1 / p=1

二級:(2,2)(2,3)(3,2)(4,2)(2,4)s=2and p>=2 / s>=2,p=2

三級:(3,3)(3,4)(4,3).  s=3, p>=3.    s=32&p>=3 / s>=3,p=3

四級:(4,4)s=32& p>=3

以上就全部的腳本及結果。在自己研究過程中參照了以下文章,如果我的文章無法滿足你的需要,可以參照下他們的,或許能給你一些靈感。:

​​禅道bug統計并釘釘機器人發送html統計詳情[複制即用]​​