# --*--coding:utf-8--*--
# updateDate = 2022/08/09
# 已离职,图表联系刘超、李一鸣调整import copy
import json
import webbrowser  # 打开网页
import random
from faker import Faker
import gevent
from gevent import monkeymonkey.patch_all()
import requests
from requests import exceptions
import re
import sqlite3
import datetime
import math
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import MultipleLocator
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
import os
from chinese_calendar import is_workday, is_holiday, get_dates, get_workdays, find_workday
import configparser
import PySimpleGUI as sg
from email.utils import parseaddr
from email.utils import formataddr
from email.header import Header
import time
from PIL import ImageGrab
import unicodedata
loglevel = "info"
plt.rcParams.update({'figure.max_open_warning': 0})month_names = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ]# 界面框架
def tool_ui(data):"""工具所有的UI布局部分:param data: 传入从配置文件tapdconf.ini读取的数据:return:布局layout""""""顶部条件输入区域"""display_bool = Truelayout_factor = [# 第一行[sg.Button(key='url_method', button_text="*    TAPD_Cookie:", size=15, button_color='#64778D',border_width=0, tooltip='点击可切换数据请求方式'),sg.Input(key='cookie', size=33, default_text=data['cookie']),sg.Button("烎", tooltip='更新项目', size=3, mouseover_colors='#7c8577', use_ttk_buttons=True),sg.Text('* 项目:', size=9, justification='right'),sg.InputCombo(key='id', size=31, default_value=data['id'], values=data['list_id'].split('_toolflag_'),enable_events=True),sg.Text('* 测试起止时间:', size=15, justification='right'),sg.Input(key='start_time', size=10, default_text=data['start_time'], tooltip='2021-01-01'),sg.Text('-', size=1),sg.Input(key='end_time', size=10, default_text=today, tooltip='2021-12-31'),sg.CalendarButton('DATE', font=('MS Sans Serif', 1, 'bold'), button_color=('#fffffb', '#11264f'),format='%Y-%m-%d',key='_CALENDAR_', target='start_time', tooltip='选择测试开始时间', month_names=month_names,enable_events=True)],# 第二行[sg.Text('迭代版本:', size=15, justification='right'),sg.InputCombo(key='iteration_id', size=31, default_value=data['iteration_id'], tooltip='根据迭代版本筛选缺陷',values=data['list_iteration'].split('_toolflag_'), enable_events=True),sg.Text('发布计划:', size=15, justification='right'),sg.InputCombo(key='release_id', size=31, default_value=data['release_id'],values=data['list_release_id'].split('_toolflag_'),tooltip='项目发布计划id', enable_events=True),# sg.Input(key='release_id', size=33, default_text=data['release_id'], tooltip='根据缺陷标题内容筛选'),sg.Text('操作系统:', size=15, justification='right'),sg.InputCombo(key='os1', size=31, default_value=data['os1'], values=data['list_os'].split('_toolflag_'),enable_events=True, tooltip='根据缺陷所选操作系统筛选,支持手输')],# 第三行[sg.Text('测试阶段:', size=15, justification='right'),sg.InputCombo(key='testphase', size=31, default_value=data['testphase'], tooltip='根据缺陷所选测试阶段筛选缺陷,支持手输',values=data['list_testphase'].split('_toolflag_'), enable_events=True),sg.Text('是否包节假日:', size=15, justification='right'),sg.Radio("是", "RADIO1", key='b2', default=(data['b2'] == 'True')),sg.Radio("否", "RADIO1", key='b1', default=(data['b1'] == 'True'), size=20),sg.Text("获取数据URL:", size=15, justification='right', visible=False),  # 留一个隐藏的占位sg.Input(key='url_tapd', size=141, default_text=data['url_tapd'], visible=False),sg.Checkbox('邮件定时(min):', key='timer', default=False),# sg.Spin([i for i in range(1, 110)], initial_value=1, size=31)sg.Slider(key='timer_slider', range=(1, 1440), default_value=120, size=(10, 10), expand_x=True,orientation='horizontal', font=('Helvetica', 10))],# 第四行[sg.Text(key='text1', text='* 邮件服务器地址:', size=15, justification='right', visible=display_bool),sg.Input(key='smtp_server', size=33, default_text=data['smtp_server'], tooltip='smtp.exmail.qq.com',visible=display_bool),sg.Text(key='text2', text='* 邮件服务器账号:', size=15, justification='right', visible=display_bool),sg.Input(key='email_user', size=33, default_text=data['email_user'], tooltip='xxxx@ztccloud.com.cn',visible=display_bool),sg.Text(key='text3', text='* 邮件服务器密码:', size=15, justification='right', visible=display_bool),sg.Input(key='email_passwd', size=33, password_char='*', default_text=data['email_passwd'],visible=display_bool)],# 第五行[sg.Text('邮件主题:', size=15, justification='right'),sg.Input(key='mail_title', size=33, default_text=data['mail_title']),sg.Text(key='text4', text='邮件收件人:', size=15, justification='right', visible=display_bool),sg.Input(key='addressee', size=33, tooltip='多个收件人用逗号隔开,邮件将直接发送至收件人,请慎重', default_text=data['addressee'],visible=display_bool),sg.Text(key='text5', text='邮件抄送人:', size=15, justification='right', visible=display_bool),sg.Input(key='mail_cc', size=33, tooltip='抄送多人用逗号隔开', default_text=data['mail_cc'], visible=display_bool)],# 按钮行[sg.Button("源码", size=10, mouseover_colors='#1d953f', use_ttk_buttons=True, right_click_menu=['&Right', ['你的队友:', '【镇山的虎】', '【远见的鹰】', '【忠诚的狗】', '【善战的狼】','而你:', '【害群的马】', '【盛饭的桶】', '【墙头的草】', ['【卸载工具】'], '【搅屎的棍】']]),sg.Text('  ', size=56, justification='left'),  # 空白区域# sg.Button(key='collapse_control', button_text="︾", size=1, button_color='#64778D', border_width=0,#           visible=True),  # 展开符号  #sg.Text('  ', size=1, justification='left'),  # 空白区域sg.Text('  ', size=31, justification='left'),  # 空白区域sg.Button("执行", bind_return_key=True, size=10, mouseover_colors='#1d953f', use_ttk_buttons=True),sg.Button("暂存", size=10, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='暂存界面输入内容'),sg.Button("清空", size=10, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='清空执行日志')]]"""邮件正文组成部分输入区域"""layout_mail_text = [[sg.Text('邮件开篇:', size=15, justification='left')],[sg.Multiline(key='daily_text', size=(75, 6), default_text=data['daily_text'])],[sg.Text('里程碑总体进度:', size=13, justification='left'),sg.Input(key='milestone_address', size=47, default_text=data['milestone_address'],tooltip='里程碑在线文档地址,用于展示'),sg.Button("贴图", size=4, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='将剪切板的截图贴到邮件里程碑位置'),sg.Button("预览", size=4, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='查看里程碑截图')],[sg.Multiline(key='milestone_excel', size=(75, 4),tooltip='可粘贴里程碑在线文档内容,请直接粘贴excel\ntip:\n    单元格合并格式只会保留第一列\n    单元格里边存在换行也会有异常..',default_text=data['milestone_excel'].replace("_toolflag_", "  "))],[sg.Text('今日测试执行清况:', size=15, justification='left'),sg.Input(key='manpower_today', size=3, default_text=data['manpower_today'], justification='right',background_color='#009ad6'),sg.Text('人天', size=4, justification='left')],[sg.Multiline(key='test_situation', size=(75, 4), default_text=data['test_situation'])],[sg.Text('明日计划:', auto_size_text=True, justification='left')],[sg.Multiline(key='tomorrow_plans', size=(75, 4), default_text=data['tomorrow_plans'])]]"""工具执行结果展示控件"""layout_result = [[sg.Output(key='debug_result', size=(75, 26), echo_stdout_stderr=True)]]"""总体布局容器"""layout = [[sg.Frame(key='frame1', title='', layout=layout_factor)],[sg.Frame(' 邮件内容:', layout_mail_text), sg.Frame(' 执行日志:', layout_result)]]return layout# 自定义url方式获取tapd数据# 界面框架
def tool_ui2(data):layout = [# 第一行[sg.Button(key='url_method', button_text="*    TAPD_Cookie:", size=15, button_color='#64778D',border_width=0),sg.Input(key='cookie', size=33, default_text=data['cookie']),sg.Text('* 测试起止时间:', size=15, justification='right'),sg.Input(key='start_time', size=10, default_text=data['start_time'], tooltip='2021-01-01'),sg.Text('-', size=1),sg.Input(key='end_time', size=10, default_text=today, tooltip='2021-12-31'),sg.CalendarButton('DATE', font=('MS Sans Serif', 1, 'bold'), button_color=('#fffffb', '#11264f'),format='%Y-%m-%d',key='_CALENDAR_', target='start_time', tooltip='选择测试开始时间', month_names=month_names,enable_events=True),sg.Text('是否包含节假日:', size=15, justification='right'),sg.Radio("是", "RADIO1", key='b2', default=(data['b2'] == 'True')),sg.Radio("否", "RADIO1", key='b1', default=(data['b1'] == 'True'))],# 第二行[sg.Text("Request Payload:", size=15, justification='right'),  # 空白区域sg.Input(key='url_tapd', size=141, default_text=data['url_tapd'],tooltip='请填写【TAPD-缺陷-系统视图-所有的】这个筛选视图添加过滤条件后调的bugs_list接口的请求体;''\n请选择原json格式粘贴,GOOGLE浏览器F12选择[view source];''\n建议筛选中添加"创建时间"条件,起止时间与界面的"测试起止时间"保持一致,有利于数据一致性'),sg.Checkbox('邮件定时(min):', key='timer', default=False, visible=False),sg.Slider(key='timer_slider', range=(1, 1440), default_value=0, size=(10, 10), expand_x=True,orientation='horizontal', font=('Helvetica', 10), visible=False)],# 第三行[sg.Text(key='text1', text='* 邮件服务器地址:', size=15, justification='right'),sg.Input(key='smtp_server', size=33, default_text=data['smtp_server'], tooltip='smtp.exmail.qq.com'),sg.Text(key='text2', text='* 邮件服务器账号:', size=15, justification='right'),sg.Input(key='email_user', size=33, default_text=data['email_user'], tooltip='xxxx@ztccloud.com.cn'),sg.Text(key='text3', text='* 邮件服务器密码:', size=15, justification='right'),sg.Input(key='email_passwd', size=33, password_char='*', default_text=data['email_passwd'])],# 第四行[sg.Text('邮件主题:', size=15, justification='right'),sg.Input(key='mail_title', size=33, default_text=data['mail_title']),sg.Text(key='text4', text='邮件收件人:', size=15, justification='right'),sg.Input(key='addressee', size=33, tooltip='多个收件人用逗号隔开,邮件将直接发送至收件人,请慎重',default_text=data['addressee']),sg.Text(key='text5', text='邮件抄送人:', size=15, justification='right'),sg.Input(key='mail_cc', size=33, tooltip='抄送多人用逗号隔开', default_text=data['mail_cc'])],# 按钮行[sg.Button("源码", size=10, mouseover_colors='#1d953f', use_ttk_buttons=True, right_click_menu=['&Right', ['你的队友:', '【镇山的虎】', '【远见的鹰】', '【忠诚的狗】', '【善战的狼】','而你:', '【害群的马】', '【盛饭的桶】', '【墙头的草】', ['【卸载工具】'], '【搅屎的棍】']]),sg.Text('  ', size=92, justification='left'),  # 空白区域sg.Button("执行", bind_return_key=True, size=10, mouseover_colors='#1d953f', use_ttk_buttons=True),sg.Button("暂存", size=10, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='暂存界面输入内容'),sg.Button("清空", size=10, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='清空执行日志')]]"""邮件正文组成部分输入区域"""layout_mail_text = [[sg.Text('邮件开篇:', size=15, justification='left')],[sg.Multiline(key='daily_text', size=(75, 6), default_text=data['daily_text'])],[sg.Text('里程碑总体进度:', size=13, justification='left'),sg.Input(key='milestone_address', size=47, default_text=data['milestone_address'],tooltip='里程碑在线文档地址,用于展示'),sg.Button("贴图", size=4, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='将剪切板的截图贴到邮件里程碑位置'),sg.Button("预览", size=4, mouseover_colors='#dec674', use_ttk_buttons=True, tooltip='查看里程碑截图')],[sg.Multiline(key='milestone_excel', size=(75, 4),tooltip='可粘贴里程碑在线文档内容,请直接粘贴excel\ntip:\n    单元格合并格式只会保留第一列\n    单元格里边存在换行也会有异常..',default_text=data['milestone_excel'].replace("_toolflag_", "  "))],[sg.Text('今日测试执行清况:', size=15, justification='left'),sg.Input(key='manpower_today', size=3, default_text=data['manpower_today'], justification='right',background_color='#009ad6'),sg.Text('人天', size=4, justification='left')],[sg.Multiline(key='test_situation', size=(75, 4), default_text=data['test_situation'])],[sg.Text('明日计划:', auto_size_text=True, justification='left')],[sg.Multiline(key='tomorrow_plans', size=(75, 4), default_text=data['tomorrow_plans'])]]"""工具执行结果展示控件"""layout_result = [[sg.Output(key='debug_result', size=(75, 26), echo_stdout_stderr=True)]]"""总体布局容器"""layout2 = [[sg.Frame(key='frame1', title='', layout=layout)],[sg.Frame(' 邮件内容:', layout_mail_text),sg.Frame(' 执行日志:', layout_result)]]return layout2# 定时器UI
def timer_ui():layout = [[sg.Text('时:分:秒', size=(20, 2), font=('Helvetica', 14))],[sg.Text('祝您顺利', size=(16, 2), font=('Helvetica', 20), justification='center', key='text_time')],[sg.Button('开始计时', button_color=('white', '#001480')),sg.Button('重新计时', button_color=('white', '#007339')),sg.Button('中止发送', button_color=('white', 'firebrick4'))]]return layoutdef GET_LIST(list1, values1):for i in list1:if isinstance(i, list):GET_LIST(i, values1)elif isinstance(i, dict):GET_DICT(i, values1)else:continuedef GET_DICT(dict1, values):"""从响应中根据key获取对应的值(第一个):param dict1: 响应结果json:param values: key:return: 查到的值(str)"""global value  # 定义全局变量value = Nonevalues1 = valuesfor k, v in dict1.items():if k == values:value = vbreakelif isinstance(v, list):  # 判断类型是不是listGET_LIST(v, values1)elif isinstance(v, dict):GET_DICT(v, values1)else:continuereturn value# 从响应中根据key获取值(所有)
def GET_VALUE_FROM_JSON_DICT(_obj, key):"""从响应中根据key获取值(所有):param _obj: 响应结果json:param key: key:return: 查到的值(list)"""ret = []def _get_value_from_json_dict(_obj, _key):if isinstance(_obj, list):for _i in _obj:_get_value_from_json_dict(_i, _key)elif isinstance(_obj, dict):for _k, _v in _obj.items():if _k == _key:ret.append(_v)else:_get_value_from_json_dict(_v, _key)else:return_get_value_from_json_dict(_obj, key)return ret# 读配置文件-[TAPD]
def read_conf(path, TAPD_list):"""读配置文件:param path: 配置文件路径:return: tmp=config.items("TAPD");list_value_conf"""try:if not os.path.exists(path):  # 判断文件是否存在if not os.path.exists(path_pic):  # 判断文件夹是否存在os.makedirs(path_pic)tapdconf_reset(path)  # 重置配置文件else:with open(path, 'r') as f:conf_text = f.read()if len(conf_text.strip()) > 0:  # 判断是否空文件config.read(path)list_sections = config.sections()# 没这个类 # 参数不一样# print(f"{config.options('TAPD')}\n{TAPD_list}")if 'TAPD' not in list_sections or set(config.options('TAPD')) != set(TAPD_list):if set(config.options('TAPD')) != set(TAPD_list):print("------>字段不一样,重置配置文件了")tapdconf_reset(path)  # 重置配置文件else:  # 空文件tapdconf_reset(path)  # 重置配置文件with open(path, 'r') as f:config.read_file(f)tmp = config.items("TAPD")  # 列表元组list_value_conf = {}  # 配置文件内容for k, v in tmp:list_value_conf[k] = vreturn list_value_confexcept ValueError as error:print(f"读配置文件异常,存在异常编码,如'✓✉☏☞'等字符,即将重置配置文件。请重启工具\n异常信息:{error}")tapdconf_reset(path)  # 重置配置文件except Exception as error:if loglevel == "debug":raise errorprint(f"读配置文件异常,存在异常编码\n异常信息:{error}")tapdconf_reset(path)  # 重置配置文件pass# 给section增加option:value
def add_option(path, section, option, value):# 给section增加option:value。# 如果section不存在就先增加一个section,option不存在就新增,存在就不操作# 不会影响已存在的section内的optiontry:with open(path, 'r') as f:config.read_file(f)if not config.has_section(section):config[section] = {option: value}else:if not config.has_option(section, option):  # 不存在optionconfig.set(section, option, value)with open(path, 'w') as f2:config.write(f2)  # 写进文件except UnicodeError as error:  # 编码异常print(f"给{section}增加{option}:{value}失败,当前内容中包含特殊符号,如'✓✉☏☞',请处理后重新操作。\n错误信息:{error}")except Exception as error:print(f"给{section}增加{option}:{value}失败\n错误信息[7]:", error)# 读section-option的值
def get_option(path, section, option):# 读section-option的值try:with open(path, 'r') as f:config.read_file(f)value = config.get(section, option)return valueexcept Exception as e:print(f"读【{section}】-【{option}】失败!错误信息:{e}")pass# 重置配置文件
def tapdconf_reset(path):"""如果配置文件被改,调此方法直接重置配置文件"""config["TAPD"] = {'cookie': '','url_tapd': '','id': '','start_time': '','smtp_server': 'smtp.exmail.qq.com','email_user': '@ztccloud.com.cn','email_passwd': '','iteration_id': '','os1': '','release_id': '','testphase': '','mail_title': '中兴新云测试日报','b2': 'False','b1': 'True','addressee': '','mail_cc': '','daily_text': '','milestone_address': '','milestone_excel': '','manpower_today': '','test_situation': '','tomorrow_plans': '','list_os': "测试环境_toolflag_UAT环境_toolflag_预发布环境_toolflag_生产环境_toolflag_项目环境",'list_id': "产品_产品需求管理--67410840",'list_iteration': "",'list_testphase': "一轮测试_toolflag_二轮测试_toolflag_验收阶段_toolflag_线上阶段_toolflag_持续测试_toolflag_通测阶段",'list_release_id': "",'win2_flag': "N"}# select_mode = ['测试环境', 'UAT环境', '预发布环境', '生产环境', '项目环境']# test_phase = ['一轮测试', '二轮测试', '验收阶段', '线上阶段', '持续测试', '通测阶段']with open(path, 'w') as f:config.write(f)# 写配置文件
def write_conf(path, section, option, value=None):"""写配置文件"""try:config.set(section, option, value)  # 写配置文件with open(path, 'w') as f:config.write(f)# print("更新配置文件[%s]" % option)except UnicodeError as error:  # 编码异常print(f"[6]当前内容中包含特殊符号,如'✓✉☏☞',请处理后重新操作。\n无法解决请联系管理员,异常[键]-[值]为:"f"[{option}]-[{value}],请处理后【重新保存界面】。\n异常信息为:{error}")except Exception as error:print(f"写配置文件(write_conf)出错,写入的[键]-[值]为:[{option}]-[{value}]\n错误信息:{error}")# 返回今天的±n个工作日
def tommorow(days=0, work_flag=True, end_time=datetime.date.today()):"""工作日标志为True时,输入非0日期返回今天的±n个工作日,输入0直接返回今日日期;工作日标志为False时,输入非0日期返回今天的±n个日子,输入0直接返回今日日期默认取下一工作日:param days::param work_flag: 工作日标志:param end_time: 测试截止日期,默认今天:return:"""if b2 == "是":  # 是包含节假日work_flag = Falseif work_flag:return find_workday(int(days), end_time)else:return end_time + datetime.timedelta(days=int(days))# 获取发布计划release_id列表
def get_release_id(cookie, id):"""根据用户cookie和项目id获取发布计划列表"""perpage = 20page = 1release_id_update_end = []while True:url = f'https://www.tapd.cn/{id}/releases/lists?page={page}&perpage={perpage}&sort_name=name&order=DESC'headers = {"accept": "*/*","accept-encoding": "gzip, deflate, br","accept-language": "zh-CN,zh;q=0.9","cache-control": "no-cache","cookie": cookie,"pragma": "no-cache","referer": f'https://www.tapd.cn/{id}/releases/lists?page={page}&perpage={perpage}&sort_name=name&order=DESC',"sec-fetch-dest": "empty","sec-fetch-mode": "cors","sec-fetch-site": "same-origin","user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.8031 SLBChan/103","x-requested-with": "XMLHttpRequest"}try:r = requests.get(url=url, headers=headers)r.encoding = 'utf-8'if "<title>登录-TAPD</title>" in r.text:print("[TAPD_Cookie]无效,请重新输入cookie后再获取")return Falselist_released_data = r.text.replace('&amp;', '&')# 正则过滤出(发布计划id, 发布计划中文名),存储成俩列表release_id_update = re.findall(rf'<div id="operation_(.*?)" class="', list_released_data, re.S)released_name_update = re.findall(rf'<td title="(.*?)">', list_released_data, re.S)if len(release_id_update) > 0 and len(release_id_update) == len(released_name_update):for i in range(len(release_id_update)):release_id_update_end.append(f"{released_name_update[i]}--{release_id_update[i]}")if len(release_id_update) < 20:return release_id_update_endpage += 1# return r.text.replace('&amp;', '&')except (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"获取数据失败,请检查字段信息[1].错误信息:{err}")return False# 获取项目id列表 20220106还能用√
def tapd_id(cookie):"""根据用户cookie获取项目id列表"""url = 'https://www.tapd.cn/company/my_take_part_in_projects_list'headers = {"accept": "*/*","accept-encoding": "gzip, deflate, br","accept-language": "zh-CN,zh;q=0.9","cache-control": "no-cache","cookie": cookie,"pragma": "no-cache","referer": "https://www.tapd.cn/67410840/prong/stories/stories_list",# https: // www.tapd.cn / 67410840 60519202/ prong / tasks"sec-fetch-dest": "empty","sec-fetch-mode": "cors","sec-fetch-site": "same-origin","user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.8031 SLBChan/103","x-requested-with": "XMLHttpRequest"}try:response = requests.get(url=url, headers=headers)if "<title>登录-TAPD</title>" in response.text:print("[TAPD_Cookie]无效,请重新输入cookie后再获取")return Falselist_object_id = re.findall(r"object-id=.(.*?)\">.*?<a title=\"(.*?)\"", response.text, re.S)for i in range(len(list_object_id)):list_object_id[i] = f'{list_object_id[i][1]}--{list_object_id[i][0]}'print("成功获取您参与的[项目]列表\n")return list_object_idexcept exceptions.ConnectTimeout as err:print("The request timed out while trying to connect to the remote server.""Requests that produced this error are safe to retry.\nerror_info:", err)return Falseexcept (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print("若网络正常,请检查TAPD_Cookie是否正常。\n错误信息:", err)return False# 获取迭代版本
def get_options(cookie, workspace_ids):"""传入版本id(workspace_ids),返回版本下的‘迭代版本名称-id’3.3壳子回炉重测(当前迭代)--1167410840001000517"""page = 1perpage = 50iteration_list = []while True:url = "https://www.tapd.cn/api/new_filter/new_filter/get_options"headers = {"Content-Type": "application/json;charset=UTF-8","Cookie": cookie,"user-agent": Faker().chrome(),"x-requested-with": "XMLHttpRequest"}data = {"entity_type": "common","workspace_ids": workspace_ids,  # 67410840"field": "iteration_id","is_system": 1,"use_scene": "search_filter","Filter": {"iteration_id": {"use_page": 1,"Filter": {"status": "~done"},"page": page,"perpage": perpage}},"dsc_token": "bGdaKSgSBhk69NTJ"}try:response = requests.post(url=url, headers=headers, data=json.dumps(data))assert response.json().get("meta").get("code") == '0'iteration_name_list = GET_VALUE_FROM_JSON_DICT(response.json().get("data"), "label")iteration_id_list = GET_VALUE_FROM_JSON_DICT(response.json().get("data"), "value")if len(iteration_name_list) == len(iteration_id_list):for index, value in enumerate(iteration_name_list):iteration_list.append(value + '--' + iteration_id_list[index])if len(iteration_name_list) >= perpage:page += 1else:return iteration_listexcept (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"获取数据失败,请检查字段信息[2].错误信息:{err}")return False# 获取缺陷字段用户视图(和缺陷列表) view_id, conf_id
def get_bug_fields_userview_and_list(cookie, workspace_ids, dsc_token='bGdaKSgSBhk69NTJ', query_token='', conf_id='',flag=None):"""获取缺陷字段用户视图和缺陷列表 view_id, conf_idworkspace_ids:项目id返回id、conf_id、当前显示字段flag:控制返回内容,默认返回全部响应jsonreturn:view_id, conf_id"""url = "https://www.tapd.cn/api/aggregation/bug_aggregation/get_bug_fields_userview_and_list"headers = {"Content-Type": "application/json;charset=UTF-8","Cookie": cookie,"user-agent": Faker().chrome(),}data = {"workspace_id": workspace_ids,"conf_id": conf_id,  # "1167410840001092960","sort_name": "","order": "","perpage": 50,"page": "1","selected_workspace_ids": "","query_token": query_token,  # "90eb0b88b2d138fbbac9b079b1df6e2c","location": "/bugtrace/bugreports/my_view","target": f"{workspace_ids}/bug/normal","entity_types": ["bug"],"use_scene": "bug_list","return_url": f"https://www.tapd.cn/tapd_fe/{workspace_ids}/bug/list?confId={conf_id}&page=1&queryToken={query_token}","dsc_token": dsc_token}try:response = requests.post(url=url, headers=headers, data=json.dumps(data))assert response.status_code == 200# total_count = GET_VALUE_FROM_JSON_DICT(response.json(), "total_count")[0]# bugslist = GET_VALUE_FROM_JSON_DICT(response.json(), "bugslist")[0][0] if total_count != 0 else {}view_info = GET_VALUE_FROM_JSON_DICT(response.json(), "view_info")[0]view_id, conf_id = view_info.get("id"), view_info.get("conf_id")return view_id, conf_idexcept (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"获取数据失败,请检查字段信息[3].错误信息:{err}")return False# 条件查询获取缺陷列表by_url
def get_bugs_list_by_url(cookie, data=None, page=1, perpage=100, flag="bugs_list", **kwargs):"""获取缺陷字段用户视图和缺陷列表"""url = "https://www.tapd.cn/api/entity/bugs/bugs_list"headers = {"Content-Type": "application/json;charset=UTF-8","Cookie": cookie,"user-agent": Faker().chrome(),}body = {}workspace_id = ''data = data.replace("true", "True").replace("false", "False").replace("null", "None")try:if isinstance(data, dict):try:body.update(data)except:sg.popup("请求非json[1],请检查", title='异常')return Falseif isinstance(data, str):try:body.update(eval(data))except:sg.popup("请求非json[2],请检查", title='异常')return Falsetry:workspace_id = body["workspace_id"]except:sg.popup("请求非json[3],请检查", title='异常')body["page"] = pagebody["perpage"] = perpageresponse = requests.post(url=url, headers=headers, data=json.dumps(body))assert response.status_code == 200total_count = GET_VALUE_FROM_JSON_DICT(response.json(), "total_count")[0]  # strbugs_list = GET_VALUE_FROM_JSON_DICT(response.json(), "Bug") if total_count != 0 else {}  # dictif flag == "bugs_list":return bugs_listelif flag == "info":view_info = GET_VALUE_FROM_JSON_DICT(response.json(), "view_info")[0]view_id, conf_id = view_info.get("id"), view_info.get("conf_id")return view_id, conf_id, workspace_id, int(total_count)else:return int(total_count)except (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"获取数据失败,请检查字段信息[4].错误信息:{err}")return False# 条件查询获取缺陷列表
def get_bugs_list(cookie, workspace_id, conf_id, start_time='', end_time='', title='', iteration_id=None, os=None,testphase=None,release_id=None, query_token='', page=1, perpage=100, flag="bugs_list"):"""获取缺陷字段用户视图和缺陷列表workspace_ids:项目id返回id、conf_id、当前显示字段flag:控制返回内容,默认返回全部响应jsonreturn:total_count:str; bugs_list:dict;"""try:if release_id is None:release_id = []elif isinstance(release_id, str):release_id = [release_id]if testphase is None:testphase = []elif isinstance(testphase, str):testphase = [testphase]if os is None:os = []elif isinstance(os, str):os = [os]if iteration_id is None:iteration_id = []elif isinstance(iteration_id, str):iteration_id = [iteration_id]created = f"{start_time} 00:00,{end_time} 23:59"url = "https://www.tapd.cn/api/entity/bugs/bugs_list"headers = {"Content-Type": "application/json;charset=UTF-8","Cookie": cookie,"user-agent": Faker().chrome(),}data = {"workspace_id": workspace_id,"conf_id": conf_id,"sort_name": "","order": "","perpage": perpage,"page": page,"selected_workspace_ids": [],"filter_expr": {"data": [{"fieldOption": "like", "fieldType": "input", "fieldSystemName": "name", "fieldDisplayName": "标题","selectOption": [], "value": title, "fieldIsSystem": "1"},{"fieldOption": "in", "fieldType": "select", "fieldSystemName": "iteration_id","fieldDisplayName": "迭代","selectOption": [], "value": iteration_id, "fieldIsSystem": "1"},{"fieldOption": "between", "fieldType": "datetime", "fieldSystemName": "created","fieldDisplayName": "创建时间","selectOption": [], "value": created, "fieldIsSystem": "1"},{"fieldOption": "in", "fieldType": "select", "fieldSystemName": "os", "fieldDisplayName": "操作系统","selectOption": [], "value": os, "fieldIsSystem": "1"},{"fieldOption": "in", "fieldType": "select", "fieldSystemName": "testphase","fieldDisplayName": "测试阶段","selectOption": [], "value": testphase, "fieldIsSystem": "1"},{"fieldOption": "in", "fieldType": "select", "fieldSystemName": "release_id","fieldDisplayName": "发布计划","selectOption": [], "value": release_id, "fieldIsSystem": "1"}],"optionType": "AND","needInit": True},"return_url": f"https://www.tapd.cn/tapd_fe/{workspace_id}/bug/list?page={page}&queryToken={query_token}","dsc_token": "PzfsAcWwfGkyMA0s"}response = requests.post(url=url, headers=headers, data=json.dumps(data))assert response.status_code == 200total_count = GET_VALUE_FROM_JSON_DICT(response.json(), "total_count")[0]  # strbugs_list = GET_VALUE_FROM_JSON_DICT(response.json(), "Bug") if total_count != 0 else {}  # dictif flag == "bugs_list":return bugs_listelse:return total_countexcept (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"获取数据失败,请检查字段信息[5].错误信息:{err}")return False# 加工数据成插入数据库的语句列表
def data_cleanup(data_tapd):"""加工tapd缺陷数据,组装成insert语句:param data_tapd::return: list_insert_sql: 插入数据库的语句列表"""try:list_insert_sql = []for Bug in data_tapd:Bug["id"] = Bug["short_id"]Bug["status"] = Bug["status"].replace('new', '新').replace('rejected', '已拒绝').replace('resolved','已解决').replace('reopened', '重新打开').replace('verified', '已验证').replace('assigned', '确认产品bug').replace('closed', '已关闭')Bug["severity"] = Bug["severity"].replace('fatal', '致命').replace('serious', '严重').replace('normal', '一般')\.replace('prompt', '提示').replace('advice', '建议') or '一般'Bug["priority"] = Bug["priority"].replace("urgent", '紧急').replace("high", '高').replace("medium", '中')\.replace("low", '低').replace("insignificant", '无关紧要').replace("9999999", "-空-")Bug["created"] = re.sub(r'([0-9].*?) .{8}', r'\1', Bug["created"]).replace('--', '')Bug["resolved"] = re.sub(r'([0-9].*?) .{8}', r'\1', Bug["resolved"]).replace('--', '')Bug["closed"] = re.sub(r'([0-9].*?) .{8}', r'\1', Bug["closed"]).replace('--', '')Bug["reject_time"] = re.sub(r'([0-9].*?) .{8}', r'\1', Bug["reject_time"]).replace('--', '')Bug["reopen_time"] = re.sub(r'([0-9].*?) .{8}', r'\1', Bug["reopen_time"]).replace('--', '')Bug["title"] = Bug["title"].replace("'", '\\"').replace("/", "\\/").replace("%", "\\%").replace("[","\\[").replace("]", "\\[")Bug["custom_field_four"] = Bug.get("custom_field_four") or '0'  # reject_numBug["reporter"] = Bug["reporter"].replace(';', '')Bug["de"] = Bug["de"].replace(';', '').replace('--', '') or Bug["reporter"]  # 不填开发就自己背着Bug["closer"] = Bug["closer"].replace(';', '').replace('--', '')Bug["fixer"] = Bug["fixer"].replace(';', '').replace('--', '')Bug["current_owner"] = Bug["current_owner"].replace(';', '').replace('--', '')sql_text = f"""INSERT INTO scores VALUES('{Bug["id"]}','{Bug["title"]}','{Bug["severity"]}','{Bug["status"]}','{Bug["current_owner"]}','{Bug["reporter"]}','{Bug["created"]}','{Bug["fixer"]}','{Bug["resolved"]}','{Bug["closed"]}','{Bug["reject_time"]}','{Bug["reopen_time"]}','{Bug["de"]}','{Bug["closer"]}','{Bug["priority"]}','{Bug["custom_field_four"]}')"""list_insert_sql.append(sql_text)return list_insert_sqlexcept Exception as err:print(f"清洗数据失败,请联系管理员[1].错误信息:{err}")return False# 获取缺陷显示字段
def get_show_fields(cookie, workspace_id, view_id):"""获取缺陷显示字段拼接的strworkspace_ids:项目idview_id:视图idreturn: "title;version_report;iteration_id;severity;priority;status;current_owner;reporter;created;bugtype;source;testtype;resolution;os;""""url = "https://www.tapd.cn/api/basic/userviews/get_show_fields"headers = {"Cookie": cookie,"user-agent": Faker().chrome(),}try:data = f'?id={view_id}&workspace_id={workspace_id}&location=/bugtrace/bugreports/my_view&form=show_fields'response = requests.get(url=url + data, headers=headers)assert response.status_code == 200field_list = GET_VALUE_FROM_JSON_DICT(response.json(), "fields")[0]return ';'.join(field_list)except (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"获取数据失败,请检查字段信息[6].错误信息:{err}")return False# 设置缺陷显示字段
def edit_show_fields(cookie, workspace_ids, view_id, custom_fields: str, dsc_token="bGdaKSgSBhk69NTJ"):"""设置缺陷显示字段workspace_ids:项目idview_id:视图idcustom_fields:视图显示字段dsc_token:不知道什么用,留个位置"""url = "https://www.tapd.cn/api/basic/userviews/edit_show_fields"headers = {"Content-Type": "application/json;charset=UTF-8","Cookie": cookie,"user-agent": Faker().chrome(),}try:data = {"workspace_id": workspace_ids,"id": view_id,"location": "/bugtrace/bugreports/my_view","custom_fields": custom_fields,"dsc_token": dsc_token}response = requests.post(url=url, headers=headers, data=json.dumps(data))assert response.status_code == 200return Trueexcept (exceptions.ConnectionError, exceptions.RequestException) as err:  # 可解决fiddler、VPN代理造成的错误问题print("连接错误,请检查网络;确认是否未开启代理工具,如Fiddler、VPN。\nerror_info:", err)return Falseexcept Exception as err:print(f"设置缺陷显示字段失败,请检查字段信息[7].错误信息:{err}")return False# 画图
def pict(tool, name, color):"""用于画图:param tool: 数据列表,格式是[[x轴下标列表],label[0],label[1],label[2],label[...]]。x轴下标列表是一定会有的,后边也是列表,数据种类取自列表label,用来画柱子高度的,与x轴下标列表长度一样,一一对应,堆积往上画柱子。:param name: label标签列表:param color: local标签颜色列表"""plt.rcParams['font.family'] = ['SimHei']  # 设置字体用于显示中文bottom = len(tool[0]) * [0]  # 堆积柱子的基底label = name  # label标签color = color  # 标签颜色i1 = 0  # label标签序列以及标签颜色序列# x=list(map(str,tool[0]))  # x轴x = tool[0]ab1 = plt.figure(figsize=(16, 6.5))  # 图形大小设置ab1.gca().spines["top"].set_color("none")  # 去图形除上边界ab1.gca().spines["left"].set_color("none")  # 去除图形左边界ab1.gca().spines["right"].set_color("none")  # 去除图形右边界plt.grid(axis='y', color='#dfdfdf')  # 设置图形网格线maxn = 0  # 最大数字for i in tool[1:]:if maxn < max(i):maxn = max(i)  # 更新最大数字plt.bar(x, i, bottom=bottom, label=label[i1], color=color[i1], zorder=10)  # 堆积设置柱子if len(tool) > 2:  # 如果是堆积柱子,则显示每个子柱子的数量,白色字体展示for a, b, c in zip(x, bottom, i):  # x:显示内容的x轴; botton+i/2:显示内容的y轴; i:显示的内容if c != 0: plt.text(a, b + c / 2, c, zorder=20, color='white')  # 如果i不等于0就显示每个堆积柱子的数字bottom = np.array(bottom) + np.array(i)  # 更新堆积柱子基底i1 += 1  # 更新label标签序列以及标签颜色序列if maxn == 0:  # 如果最大数字为0,说明没有一个柱子高度有值的,则没必要生成图片,退出函数print(f"图表【{name[-1]}】无数据,无需生成               <-----")returnab1.gca().yaxis.set_major_locator(MultipleLocator(math.ceil(maxn / 7)))  # 设置Y轴刻度间隔# plt.ylim(0, maxn+4)for a, b in zip(x, bottom):plt.text(a, b + 0.05, b, fontsize=12)  # 展示每列总数plt.title(name[-1], fontsize=16, y=1.1)  # 设置图形标题plt.legend(loc=(1.01, 0.9))  # 设置label标签位置if len(tool[0]) > 13:  # 如果x轴内容大于13,则x刻度旋转50度plt.xticks(rotation=50)print(f"生成【{name[-1]}】成功    √")path_savefig = path_pic + rf"{name[-1]}.jpg"  # 保存到跟配置文件一个路径下plt.savefig(path_savefig, bbox_inches='tight', pad_inches=0.3)  # 保存图片# plt.show()plt.close()# 分析数据、存表、并画图
def analyze_datas(list_sql, list_date, id):"""分析数据、存表、并画图:param list_sql: list_sql:param time_line: 时间轴:param id: 项目id:return:numbs:一些‘统计数据’以及两个‘html形式的清单’组成的列表"""# 连接内存数据库conn = sqlite3.connect(':memory:')# 创建数据库游标cur = conn.cursor()today = end_time  # 邮件实际发送日期  # 全局变量# 建表的sql语句buginfo_table = '''CREATE TABLE scores(id TEXT,title TEXT,severity TEXT,status TEXT,current_owner TEXT,reporter TEXT,created TEXT,fixer TEXT,resolved TEXT,closed TEXT,reject_time TEXT,reopen_time TEXT,de TEXT,closer TEXT,priority TEXT,reject_num TEXT);'''cur.execute(buginfo_table)  # 执行建表的sql语句for sql in list_sql:  # 插入缺陷数据cur.execute(sql)def exec(sql):  # 执行sql语句方法cur.execute(sql)return cur.fetchall()def get_numb(condition):  # 生成趋势图方法c3 = [0] * len(list_date)for index, date_choiced in enumerate(list_date):c3[index] += exec(f"SELECT count(*) from scores where {condition.replace('%date%', date_choiced)};")[0][0]return c3# 每日新增BUG数趋势图√pict([list_date, get_numb("created='%date%'")], ['新增缺陷数', '每日新增BUG数趋势图'], ['#63B8FF'])# pict([list_date, get_numb("closed='%date%'")], ['关闭缺陷数', '每日关闭缺陷数趋势图'], ['#63B8FF'])# pict([list_date, np.array(get_numb("status not in ('重新打开','新','确认产品bug') and  resolved='%date%'")) +#       np.array(get_numb("status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='%date%'")) +#       np.array(get_numb(#           "status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed='%date%'"))],#      ['修复缺陷数', '每日修复缺陷数趋势图'], ['#63B8FF'])# # 每日未关缺陷数趋势图# pict([list_date, get_numb("created<='%date%' and (closed>'%date%' or closed='')")], ['未关闭缺陷数', 'testphoto_每日未关缺陷数趋势图'],#      ['#63B8FF'])# 每日剩余BUG趋势图√# 每日未关闭总数量# 开发已解决待回归的# 开发未解决num_all, num_solved, num_new = [0] * len(list_date), [0] * len(list_date), [0] * len(list_date)for index, date_choiced in enumerate(list_date):date_choiced = str(date_choiced)# 保证创建时间早于等于当天# 保证当天还未关闭def add1times(reject_time, resolved, reopen_time, date_choiced):# 计算是否在这个日子里是不是处于处理了的状态if reopen_time == '':  # 重新打开时间为空if (resolved <= date_choiced and resolved != '') or (reject_time <= date_choiced and reject_time != ''):return 1else:  # reopen_time!=''if reopen_time <= date_choiced:if reopen_time <= reject_time <= date_choiced:return 1elif reopen_time <= resolved <= date_choiced:return 1else:if (reject_time <= date_choiced and reject_time != '') or (resolved <= date_choiced and resolved != ''):return 1return 0sql_ads = f'''SELECT reject_time, resolved, reopen_time from scores where created<='{date_choiced}' and (closed='' or closed>'{date_choiced}')'''listthis = exec(sql_ads)num_all[index] = len(listthis)num = 0for i in listthis:num += add1times(i[0], i[1], i[2], date_choiced)num_solved[index] = numif date_choiced == today:  # 最新一天的数据,基于特殊照顾,保证绝对准确sql_two = f'''SELECT count(*) from scores where created<='{date_choiced}'and status in ('已验证','已解决','已拒绝')and (closed='' or closed>'{date_choiced}')'''num_solved[index] = exec(sql_two)[0][0]num_new[index] += num_all[index] - num_solved[index]pict(tool=[list_date, num_solved, num_new], name=["已解决测试未关闭", "开发尚未解决", "每日剩余BUG趋势图"], color=["#90EE90", "#ed7158"])# today = '2021-08-20'def get_numb1(list_people, condition):"""生成缺陷处理人/创建人分布图方法:param list_column: 人员列表,处理后用来替换【人员】flag1的:param factor_sql:统计缺陷数据的where条件语句:param label:where条件语句的其中一个条件的所有值;替换label[j]。最后一个元素存储图表名称用于pict方法画柱状图。:param color:传给pict方法画柱状图,柱子的颜色:return:"""if len(list_people) == 0:print(f"图表【{label[-1]}】无数据,无需生成               <-----")returnlist_date = list(zip(*list_people))[0]c3 = [[[0] for _ in range(len(list_date))] for _ in range(len(label) - 1)]for i in range(len(list_date)):for j in range(len(label) - 1):sql_exec = f"SELECT count(*) from scores where {condition.replace('%date%', str(list_date[i]))}='{label[j]}';"c3[j][i] = exec(sql_exec)[0][0]pict([list(list_date)] + c3, label, color)# '[list(list_date)] + c3': 数据列表,格式是[[x轴下标列表], [严重缺陷个数列表], [一般缺陷个数列表], [建议缺陷个数列表]]。# x轴下标列表是一定会有的,在这就是人员列表,后边也是列表,数据种类取自列表label,用来画柱子高度的,与x轴下标列表长度一样,一一对应,堆积往上画柱子。return"""测试数据"""# 测试今日创建BUG数分布图√label = ["建议", "提示", "一般", "严重", "致命", "今日创建BUG数分布图"]  # 标签参数,严重程度  # 顺序敏感color = ["#e7e5e5", "#90EE90", "#fed455", "#e9a661", "#ed7158"]  # 颜色参数sql_1 = f"select reporter from scores where created='{today}' group by reporter order by count(*) desc"  # 今天关闭的缺陷的创建人sql_2 = f"created='{today}' and reporter='%date%' and severity"get_numb1(exec(sql_1), sql_2)# 测试今日关闭BUG数分布图√label = ["已关闭", "今日关闭BUG数分布图"]  # 标签参数  # 顺序敏感color = ["#63B8FF"]  # 颜色参数sql_1 = f"select reporter from scores where closed='{today}' group by reporter order by count(*) desc"  # 今天关闭的缺陷的创建人sql_2 = f"closed='{today}' and reporter='%date%' and status"get_numb1(exec(sql_1), sql_2)# 测试创建BUG总数分布图√label = ["建议", "提示", "一般", "严重", "致命", "创建BUG总数分布图"]  # 标签参数,严重程度  # 顺序敏感color = ["#e7e5e5", "#90EE90", "#fed455", "#e9a661", "#ed7158"]  # 颜色参数sql_1 = f"select reporter from scores group by reporter order by count(*) desc"  # 今天关闭的缺陷的创建人sql_2 = f"reporter='%date%' and severity"get_numb1(exec(sql_1), sql_2)# 测试待处理BUG对应处理人分布图label = ["已验证", "已解决", "已拒绝", "待处理BUG对应处理人分布图"]  # 顺序敏感color = ["#90EE90", "#fed455", "#ed7158"]sql_1 = f"select current_owner from scores where status in ('已验证','已解决','已拒绝') group by current_owner order by count(*) desc"sql_2 = f"current_owner='%date%' and status"get_numb1(exec(sql_1), sql_2)"""开发数据"""def get_numb2(list_sql, sql_2):  # 生成开发今日解决BUG数方法if len(list_sql) == 0:print(f"图表【{label[-1]}】无数据,无需生成               <-----")returnlist_date = list(zip(*list_sql))[0]c3 = [[[0] for _ in range(len(list_date))] for _ in range(len(label) - 1)]for i in range(len(list_date)):for j in range(len(label) - 1):c3[j][i] = \exec(f"SELECT count(*) from scores where {sql_2[j]}='{list_date[i]}';")[0][0]pict([list(list_date)] + c3, label, color)return# 生成开发今日产生BUG数分布图label = ["转需求", "已拒绝", "已解决", "保持为新", "被重新打开", "今日产生BUG数分布图"]  # 标签参数  # 顺序敏感color = ["#90EE90", "#e7e5e5", "#63B8FF", "#fed455", "#ed7158"]  # 颜色参数sql_1 = f"select * from (select fixer as names from scores where status not in ('重新打开','新','确认产品bug') and created='{today}' and resolved!='' union all  select de as names from scores where status not in ('重新打开','新','确认产品bug') and created='{today}' and  resolved='' and reject_time!='' union all select closer as names from scores where status not in ('重新打开','新','确认产品bug') and created='{today}' and  resolved='' and reject_time='' and closed!='' union all select current_owner as names from scores where status in ('新','重新打开','确认产品bug') and created='{today}') group by names order by count(*) desc"sql_2 = [f"status not in ('重新打开','新','确认产品bug') and created='{today}' and  resolved='' and reject_time='' and closed!='' and closer",f"status not in ('重新打开','新','确认产品bug') and created='{today}' and resolved='' and reject_time!='' and de",f"status not in ('重新打开','新','确认产品bug') and created='{today}' and resolved!='' and fixer",f"status in ('新','确认产品bug') and created='{today}' and current_owner",f"status in ('重新打开') and created='{today}' and current_owner"]get_numb2(exec(sql_1), sql_2)# 生成开发今日解决BUG数分布图label = ["已解决", "已拒绝", "转需求", "今日解决BUG数分布图"]  # 标签参数  # 顺序敏感color = ["#63B8FF", "#e7e5e5", "#90EE90"]  # 颜色参数sql_1 = f"select * from (select fixer as names from scores where status not in ('重新打开','新','确认产品bug') and resolved='{today}' union all  select de from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='{today}' union all select closer from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed='{today}') group by names order by count(*) desc"sql_2 = [f"status not in ('重新打开','新','确认产品bug') and resolved='{today}' and fixer",f"status not in ('重新打开','新','确认产品bug') and resolved='' and reject_time='{today}' and de",f"status not in ('重新打开','新','确认产品bug') and resolved='' and reject_time='' and closed='{today}' and closer"]get_numb2(exec(sql_1), sql_2)# 生成开发累计解决BUG分布图label = ["已解决", "已拒绝", "转需求", "累计解决BUG分布图"]  # 标签参数  # 顺序敏感color = ["#63B8FF", "#e7e5e5", "#90EE90"]  # 颜色参数sql_1 = f"select * from (select fixer as names from scores where status not in ('重新打开','新','确认产品bug') and resolved!='' union all  select de from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time!='' union all select closer from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed!='') group by names order by count(*) desc"sql_2 = [f"status not in ('重新打开','新','确认产品bug') and resolved!='' and fixer",f"status not in ('重新打开','新','确认产品bug') and resolved='' and reject_time!='' and de",f"status not in ('重新打开','新','确认产品bug') and resolved='' and reject_time='' and closed!='' and closer"]get_numb2(exec(sql_1), sql_2)# 生成累计BUG对应处理人分布图label = ["转需求", "已拒绝", "已解决", "保持为新", "被重新打开", "累计BUG对应处理人分布图"]  # 标签参数  # 顺序敏感color = ["#90EE90", "#e7e5e5", "#63B8FF", "#fed455", "#ed7158"]  # 颜色参数sql_1 = f"select * from (select fixer as names from scores where status not in ('重新打开','新','确认产品bug') and resolved!='' union all  select de from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time!='' union all select closer from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed!='' union all select current_owner from scores where status in ('新','重新打开','确认产品bug')) group by names order by count(*) desc"sql_2 = [f"status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed!='' and closer",f"status not in ('重新打开','新','确认产品bug') and resolved='' and reject_time!='' and de",f"status not in ('重新打开','新','确认产品bug') and resolved!='' and fixer",f"status in ('新','确认产品bug') and current_owner",f"status in ('重新打开') and current_owner"]get_numb2(exec(sql_1), sql_2)# 生成累计BUG对应开发人员分布图label = ["转需求", "已拒绝", "已解决", "保持为新", "被重新打开", "累计BUG对应开发人员分布图"]  # 标签参数  # 顺序敏感color = ["#90EE90", "#e7e5e5", "#63B8FF", "#fed455", "#ed7158"]  # 颜色参数sql_1 = f"select * from (select de as names from scores where status not in ('重新打开','新','确认产品bug') and resolved!='' union all  select de as names from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time!='' union all select de as names from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed!='' union all select de as names from scores where status in ('新','重新打开','确认产品bug')) group by names order by count(*) desc"sql_2 = [f"status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed!='' and de",f"status not in ('重新打开','新','确认产品bug') and resolved='' and reject_time!='' and de",f"status not in ('重新打开','新','确认产品bug') and resolved!='' and de",f"status in ('新','确认产品bug') and de",f"status in ('重新打开') and de"]get_numb2(exec(sql_1), sql_2)# 重复打开缺陷个数排序label = ["返工一次", "返工两次", "两次以上", "重复打开BUG数对应开发人员分布图"]  # 标签参数  # 顺序敏感color = ["#63B8FF", "#fed455", "#ed7158"]  # 颜色参数sql_1 = f"select * from (select de as names from scores where reject_num not in ('0', 0, '', '--')) group by names order by count(*) desc"sql_2 = [f"reject_num = '1' and de",f"reject_num = '2' and de",f"reject_num not in ('0', 0, '', '--', '1', '2') and de"]get_numb2(exec(sql_1), sql_2)# 生成开发待解决BUG对应处理人分布图√if id == "67410840":  # 如果是产品迭代,则没有'确认产品bug'label = ["重新打开", "新", "待解决BUG对应处理人分布图"]  # 顺序敏感color = ["#ed7158", "#fed455"]sql_1 = f"select current_owner from scores where status in ('重新打开','新')  group by current_owner order by count(*) desc"sql_2 = f"current_owner='%date%' and status"get_numb1(exec(sql_1), sql_2)  # 待解决缺陷对应处理人分布图else:  # 其他项目则存在确认产品bug状态的缺陷label = ["重新打开", "确认产品bug", "新", "待解决BUG对应处理人分布图"]  # 顺序敏感color = ["#ed7158", "#e7e5e5", "#fed455"]sql_1 = f"select current_owner from scores where status in ('重新打开','确认产品bug','新')  group by current_owner order by count(*) desc"sql_2 = f"current_owner='%date%' and status"get_numb1(exec(sql_1), sql_2)  # 待解决缺陷对应处理人分布图def get_numb3(list_sql, pic_name):"""生成重新打开缺陷清单、阻塞缺陷清单的表格:param list_sql: 查询表格数据sql:param pic_name: 图表名称:return:"""html = '<table border=1 cellpadding="6px"><tr bgcolor="#9bdde9"><td style=\'width:80px\' align=\'center\'>缺陷ID</td><td style=\'width:450px\' align=\'center\'>缺陷标题</td><td style=\'width:120px\' align=\'center\'>缺陷处理人</td><td style=\'width:120px\' align=\'center\'>缺陷创建人</td></tr>'if len(list_sql) == 0:print(f"图表【{pic_name}】无数据,无需生成               <-----")html = '<p>暂无</p>'else:print(f"生成【{pic_name}】成功    √")for i in range(len(list_sql)):html = f'{html}<tr><td align=\'center\' \'width:80px\'>{list_sql[i][0]}</td><td style=\'width:450px\'>{list_sql[i][1]}</td><td style=\'width:120px\' align=\'center\'>{list_sql[i][2]}</td><td style=\'width:120px\' align=\'center\'>{list_sql[i][3]}</td></tr>'html = html + '</table>'  # f'{html}</table>'return htmldef get_numb4(list_sql, pic_name):"""生成阻塞缺陷清单的表格:param list_sql: 查询表格数据sql:param pic_name: 图表名称:return:"""html = '<table border=1 cellpadding="6px"><tr bgcolor="#9bdde9"><td style=\'width:80px\' align=\'center\'>缺陷ID</td><td style=\'width:450px\' align=\'center\'>缺陷标题</td><td style=\'width:120px\' align=\'center\'>缺陷处理人</td><td style=\'width:120px\' align=\'center\'>缺陷创建人</td><td style=\'width:120px\' align=\'center\'>阻塞天数</td></tr>'if len(list_sql) == 0:print(f"图表【{pic_name}】无数据,无需生成               <-----")html = '<p>暂无</p>'else:print(f"生成【{pic_name}】成功    √")for i in range(len(list_sql)):daysed = (datetime.datetime.strptime(today, "%Y-%m-%d") - datetime.datetime.strptime(list_sql[i][4],"%Y-%m-%d")).dayshtml = f'{html}<tr><td align=\'center\' \'width:80px\'>{list_sql[i][0]}</td><td style=\'width:450px\'>{list_sql[i][1]}</td><td style=\'width:120px\' align=\'center\'>{list_sql[i][2]}</td><td style=\'width:120px\' align=\'center\'>{list_sql[i][3]}</td><td style=\'width:120px\' align=\'center\'>{str(daysed)}</td></tr>'html = html + '</table>'  # f'{html}</table>'return html# 生成阻塞缺陷清单sql_1 = "select id,title,current_owner,reporter,created from scores where priority in ('紧急','高') and status in ('重新打开','确认产品bug','新') order by created;"mail1 = get_numb4(exec(sql_1), '阻塞缺陷清单')# 生成重新打开缺陷清单sql_1 = "select id,title,current_owner,reporter from scores where status='重新打开';"mail2 = get_numb3(exec(sql_1), '重新打开缺陷清单')numbs = []numbs.append(exec("select count(*) from scores;")[0][0])  # 缺陷总数 1numbs.append(exec(f"select count(*) from (select fixer as names from scores where status not in ('重新打开','新','确认产品bug') and resolved='{today}' union all  select de from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='{today}' union all select closer from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed='{today}');")[0][0])  # 今日开发解决bug数 2numbs.append(exec("select count(*) from (select fixer as names from scores where status not in ('重新打开','新','确认产品bug') and resolved!='' union all  select de from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time!='' union all select closer from scores where status not in ('重新打开','新','确认产品bug') and  resolved='' and reject_time='' and closed!='');")[0][0])  # 开发累计解决bug数 3numbs.append(exec("select count(*) from scores where priority in ('紧急','高') and status in ('重新打开','确认产品bug','新');")[0][0])  # 阻塞问题总数 4numbs.append(mail1)  # 阻塞问题 5numbs.append(exec("select count(*) from scores where status='重新打开'")[0][0])  # 重新打开问题总数 6numbs.append(mail2)  # 重新打开缺陷清单 7numbs.append(exec(f"select count(*) from scores where created='{today}' ;")[0][0])  # 今日测试提交缺陷数 8numbs.append(exec(f"select count(*) from scores where status in ('已验证','已解决','已拒绝') ;")[0][0])  # 截止目前待测试回归缺陷数 9numbs.append(exec(f"select count(*) from scores where status in ('重新打开','确认产品bug','新') ;")[0][0])  # 截止目前待开发解决缺陷数 10numbs.append(exec(f"select count(*) from scores where closed='{today}';")[0][0])  # 今日测试关闭数 11numbs.append(exec(f"select count(*) from scores where reject_num not in ('0', 0, '', '--');")[0][0])  # 重复打开缺陷数 12# 关闭游标cur.close()# 关闭连接conn.close()return numbs# 获取日期 b2:是否包含节假日
def get_date(start_time, end_time, has_holiday):"""根据是否过滤节假日标志,返回起止日期之间的所有日期:param start_time:起日期:param end_time:止日期:param has_holiday: 是否包含节假日,是/否,str:return:list_date:日期列表"""# return:time_line:时间轴列表# end_time = datetime.date.today() if end_time == '' else datetime.date(*map(int, end_time.split("-")))list_date = []start_time = datetime.date(*map(int, start_time.split("-")))end_time = datetime.date(*map(int, end_time.split("-")))  # 使用邮件实际发送日期赋值  end_time:全局变量if end_time < start_time:end_time = datetime.date(*map(int, str(datetime.date.today()).split("-")))  # 取今天的end_timeif has_holiday == "是":list_date.extend([str(i) for i in get_dates(start_time, end_time)])# while start_time <= end_time:#     list_date.append(str(start_time))#     start_time = start_time + datetime.timedelta(days=1)#     continueelse:list_date.extend([str(i) for i in get_workdays(start_time, end_time)])# while start_time <= end_time:#     if is_workday(start_time):#         list_date.append(str(start_time))#     start_time = start_time + datetime.timedelta(days=1)#     continuereturn list_date# 解析邮件地址,以保证邮有别名可以显示
def __format_addr__(addr):# 解析邮件地址,以保证邮有别名可以显示alias_name, addr = parseaddr(addr)# 防止中文问题,进行转码处理,并格式化为str返回return formataddr((Header(alias_name, charset="utf-8").encode(),addr))# 发送邮件方法
def send_mail(user, passwd, list_addressee, list_mail_cc, smtp_server, numbs, list_value):""":param user: 发件人账号:param passwd: 发件人密码:param addressee: 收件人账号列表:list:param mail_cc: 抄送人账号列表:list:param smtp_server: 邮箱服务器地址:param numbs:一些‘统计数据’以及两个‘html形式的清单’组成的列表:param list_value::return:"""def check1(image_name, site):  # 检测图片是否存在,根据结果选择HTML代码"""path_pic:图片存储路径,全局变量image_name:图片名称site:用于生成html的图片展位符"""if os.path.exists(path_pic + image_name):return f'<p><img src="cid:{site}"></p>'else:if image_name == '里程碑截图.jpg':return ''elif image_name == '每日新增BUG数趋势图.jpg':return '<p>暂无BUG记录</p>'else:return '<p>暂无</p>'# 邮件实际发送日期的下一个工作日end_time_int = datetime.date(*map(int, end_time.split("-")))next_workday = end_time_int + datetime.timedelta(days=int(1))if list_date_Source == "chinese_calendar":while is_holiday(next_workday):next_workday = next_workday + datetime.timedelta(days=int(1))html_style = '''<style>#mingpian{width:350px;height:110px;border-radius:5px;background-color:#2F4F4F;font-family:"微软雅黑";}#msg {width:350px;height:30px;font-size:16px;color:#000000    ;padding-top:10px;background-color:#F5F5F5;}#msg mail{float:center;font-family:STLiti,FZShuTi,STHupo,STXinwei,"Simhei",STKaiti;font-size:22px;font-weight:bold;   }#logoimg{padding-top:10px;float:center;width:350px;height:80px;}#logoimg img{width:200px;height:30px;}.clearDiv{clear:both;}</style>
'''# <pre>{list_value[5]}<br/></pre># image13html = f'''<html><head></head><body><pre>{list_value[0]}</pre><hr style="height:1px;border:none;border-top:1px solid #DCDCDC;" align="left"/><h2>一、里程碑总体进度</h2><a href={list_value[4]} target="_blank" title={list_value[4]}>{list_value[4]}</a><br/>{list_value[5]}{check1('里程碑截图.jpg', 'image8')}<h2>二、每日BUG趋势</h2><p><b>每日新增缺陷趋势,期间累计总数: {numbs[0]}</b></p>{check1('每日新增BUG数趋势图.jpg', 'image1')}    <p><b>每日剩余缺陷趋势:{numbs[8]+numbs[9]}</b></p>{check1('每日剩余BUG趋势图.jpg', 'image14')}<h2>三、阻塞问题:{numbs[3]}</h2>{numbs[4]}<h2>四、待解决的重新打开状态BUG:{numbs[5]},已定性的返工bug数共计:{numbs[11]}</h2>{numbs[6]}{check1('重复打开BUG数对应开发人员分布图.jpg', 'image13')}<h2>五、今日测试投入情况:{list_value[3]}人天</h2><pre>{list_value[1]}</pre><h2>六、开发+需求缺陷数据统计</h2><p><b>今日新增BUG:{numbs[7]}</b></p>{check1('今日产生BUG数分布图.jpg', 'image4')}<p><b>今日解决BUG:{numbs[1]}</b></p>{check1('今日解决BUG数分布图.jpg', 'image3')}<p><b>待解决BUG:{numbs[9]}</b></p>{check1('待解决BUG对应处理人分布图.jpg', 'image2')}<p><b>累计解决BUG:{numbs[2]}</b></p>{check1('累计解决BUG分布图.jpg', 'image5')}<p><b>累计BUG:{numbs[0]}</b></p>{check1('累计BUG对应处理人分布图.jpg', 'image6')}{check1('累计BUG对应开发人员分布图.jpg', 'image12')}<h2>七、测试缺陷数据统计</h2><p><b>今日创建BUG:{numbs[7]}</b></p>{check1('今日创建BUG数分布图.jpg', 'image7')}<p><b>今日关闭BUG:{numbs[10]}</b></p>{check1('今日关闭BUG数分布图.jpg', 'image9')}<p><b>待处理BUG:{numbs[8]}</b></p>{check1('待处理BUG对应处理人分布图.jpg', 'image10')}<p><b>测试创建BUG总数:{numbs[0]}</b></p>{check1('创建BUG总数分布图.jpg', 'image11')}<hr style="height:1px;border:none;border-top:1px solid #DCDCDC;" align="left"/><h2>明日({next_workday})测试计划:</h2><pre>{list_value[2]}</pre><br /><br /><hr style="height:1px;border:none;border-top:1px solid #B5C4DF;" width="350" align="left"/><body align="left"><div id="mingpian"  align="center"><div id="msg"><a><mail>{user}</mail></a></div><div id="logoimg"><a href="http://ztessc.cn/"><img src="http://qty83k.creatby.com/materials/33005/hd/5792bc4acc4bc84aeb64f29795ef5561_4096.png"/></a></div>{html_style}<div class="clearDiv"></div></div></body></body></html><style>table,table tr th, table tr td {{ border:1px solid #0094ff; }}table {{ border-collapse: collapse; }}  </style>'''smtp_server = smtp_serverSMTP_PORT = 25msg = MIMEMultipart()msg.attach(MIMEText(html, 'html', 'utf-8'))def addimages(image_location, image_id):fp = open(image_location, 'rb')msgImage = MIMEImage(fp.read())fp.close()msgImage.add_header('Content-ID', image_id)return msgImage# 按位置给邮件插入图片def check(a, b):  # 如果图片存在,则添加图片到邮件if os.path.exists(a):msg.attach(addimages(a, b))else:returncheck(path_pic + '每日新增BUG数趋势图.jpg', 'image1')check(path_pic + '每日剩余BUG趋势图.jpg', 'image14')check(path_pic + '里程碑截图.jpg', 'image8')# 开发check(path_pic + '待解决BUG对应处理人分布图.jpg', 'image2')check(path_pic + '今日解决BUG数分布图.jpg', 'image3')check(path_pic + '今日产生BUG数分布图.jpg', 'image4')check(path_pic + '累计解决BUG分布图.jpg', 'image5')check(path_pic + '累计BUG对应处理人分布图.jpg', 'image6')check(path_pic + '累计BUG对应开发人员分布图.jpg', 'image12')check(path_pic + '重复打开BUG数对应开发人员分布图.jpg', 'image13')# 测试check(path_pic + '今日创建BUG数分布图.jpg', 'image7')check(path_pic + '今日关闭BUG数分布图.jpg', 'image9')check(path_pic + '待处理BUG对应处理人分布图.jpg', 'image10')check(path_pic + '创建BUG总数分布图.jpg', 'image11')# 格式化展示收件人strFrom = __format_addr__(user)msg['From'] = strFrom# 格式化展示发件人strTo = list()# 原来是一个纯邮箱的list,现在如果是一个["jaychen<jaychen@jay.com>"]的list给他格式化try:for a in list_addressee:strTo.append(__format_addr__(a))except Exception as e:# 没有对a和toadd进行type判断,出错就直接还原strTo = list_addresseemsg['To'] = ','.join(strTo)  # 这个一定要是一个str,不然会报错“AttributeError: 'list' object has no attribute 'lstrip'”# 格式化展示抄送人strCC = list()# 原来是一个纯邮箱的list,现在如果是一个["jaychen<jaychen@jay.com>"]的list给他格式化try:for a in list_mail_cc:if a not in list_addressee:strCC.append(__format_addr__(a))except Exception as e:# 没有对a和toadd进行type判断,出错就直接还原strCC = list_mail_ccmsg['cc'] = ','.join(strCC)# msg['To'] = formataddr(("收件人", user))  # 括号里的对应收件人邮箱昵称、收件人邮箱账号# msg['cc']=ccmsg['Subject'] = list_value[6]  # "中兴新云测试日报"  # 构建msg对象的邮件主题def del_img(f):return f[-4:] == ".jpg"try:smtp_server = smtplib.SMTP(smtp_server, SMTP_PORT)  # 连接SMTP服务器except Exception as err:print('\n邮件发送失败,请确认[邮件服务器地址]是否可连接。\n错误信息:{0}'.format(err))returnflag = False  # 返回发件结果try:smtp_server.ehlo()smtp_server.starttls()smtp_server.ehlo()smtp_server.login(user, passwd)  # 身份认证addressee_all = list_addressee + list_mail_cc  # 最终收件人=收件人+抄送人addressee_all = list(set(addressee_all))  # list去重smtp_server.sendmail(user, addressee_all, msg.as_string())  # 利用msg的as_string()方法转换成字符串print('\n发送邮件成功,请前往邮箱:【{0}】已发送邮件中查看'.format(user))flag = Trueexcept smtplib.SMTPAuthenticationError as err:print('\n邮箱身份认证失败,请确认[邮件服务器账号/密码]是否正确。\n错误信息:{0}\n'.format(err))except smtplib.SMTPSenderRefused as err:print('\nSender address refused.\n错误信息:{0}\n'.format(err))except smtplib.SMTPDataError as err:print('\nSMTP 服务器不接受数据。\n错误信息:{0}\n'.format(err))except smtplib.SMTPConnectError as err:print('\n连接建立期间出错。\n错误信息:{0}\n'.format(err))except Exception as err:if isinstance(err, UnicodeEncodeError):print("\n邮件发送失败! [邮件服务器账号/密码/收件人/抄送人]输入了异常字符,比如中文。\n错误信息:", err)else:print('\n邮件发送失败,请确认邮箱配置。\n错误信息:{0}\n'.format(err))finally:smtp_server.quit()# 删除图片del_img1 = list(filter(del_img, os.listdir(path_pic)))  # 过滤出要删的图片列表for i in del_img1:  # 删除图片if 'testphoto_' not in i:  # 测试图片带testphoto_的不删i = path_pic + ios.remove(i)if flag:return True# 模糊匹配方法
def search_result(list_old, str):"""根据str筛选列表各项,返回包含str的项组成的新列表list_old:原列表;str:过滤字段,返回过滤后的字段return 筛选数据后的列表"""return list((filter(lambda seq: re.search(str, seq) is not None, list_old)))# 输入框格式检查
def input_check(key_data):if key_data.isdigit() is False:  # 不是纯数字return Truereturn False# 测试开始时间校验
def date_check(date):"""校验date格式为 YYYY-mm-dd,且早于当天日期。超过120天弹提示框。如未符合以上所有条件,返回True"""try:today = datetime.datetime.today().strftime('%Y-%m-%d')  # 获取今日日期:strtoday = datetime.datetime.strptime(today, "%Y-%m-%d")  # str转datetimeif len(date) == 10:r = re.compile('^\d{4}-\d{2}-\d{2}')  # 正则规则,yyyy-mm-ddif r.match(date) is None:sg.popup("请确认[测试开始时间]格式正确", title='提示')return Trueelse:sg.popup("请确认[测试开始时间]格式正确", title='提示')return Truestart_time_datetime = datetime.datetime.strptime(date, "%Y-%m-%d")  # str转datetimeif (start_time_datetime - today).days > 0:sg.popup("[测试开始时间]请选择今日之前的日期", title='提示')return Trueif (start_time_datetime - today).days < -120:button_return = sg.popup_ok_cancel("测试开始日期距今超过120天,\n会影响图表展示效果,\n是否继续?")if button_return == 'Cancel' or button_return is None:return Trueexcept Exception:sg.popup("请检查[测试开始时间]格式是否正确", title='提示')# 检查网络状态
def bad_net(url='https://www.baidu.com'):r = requests.get(url, timeout=3)# assert r.status_code == 200, r.status_code# 检查网络状态
def checkNet_process():"""检查网络状态"""headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',}try:response = requests.head('https://www.tapd.cn/company/participant_projects?from=left_tree2', headers=headers,timeout=5)  # url, headers=headers)if response.status_code != 200:print('网络无法访问,状态码:{0}'.format(response.status_code))# else:#     print('网络正常访问')except requests.exceptions.ConnectTimeout as error:sg.popup("网络连接超时!\n请确保代理工具如Fiddler、VPN等关闭、网络正常", title='警告')print('网络连接超时,请确保代理工具如Fiddler、VPN等关闭、网络正常。\n错误信息:', error)passexcept requests.exceptions.ConnectionError as error:sg.popup("主机网络异常!\n请确保代理工具如Fiddler、VPN等关闭、网络正常", title='警告')print('主机网络异常,请确保代理工具如Fiddler、VPN等关闭、网络正常。\n错误信息:', error)passexcept Exception as error:sg.popup("主机网络异常!\n将导致程序网络响应阻塞", title='警告')print('网络异常报错,请确保代理工具如Fiddler、VPN等关闭、网络正常。\n错误信息:', error)# 获取应该合并行数
def get_n_num(list, idx):"""获取首列应该合并行数"""num = 1  # 默认合并一行for i in list[idx + 1:]:if i[0] == '':num += 1else:return numreturn num# 创建html中的table标签
def text_to_table(milestone_list, milestone_excel):"""创建html中的table标签:param milestone_list: 一个以行元素组成的列表为元素的列表,list[list,list]:param milestone_excel: 里程碑内容输入框的原始值,string:return: html的table标签的内容,text"""td_line = ''num_row = 0  # 存第一行的列数num = 0  # 存最后一行的列数与第一行的差table_text = '<table border="1" bordercolor="#000000" cellspacing="0" cellpadding="2" style="border-collapse:collapse;">'  # 存储table标签list_n_num = []for i_1 in milestone_list:# 获得一个子项为列表的列表tr_list_t = i_1.split('\t')list_n_num.append(tr_list_t)for inx1, val in enumerate(list_n_num):  # 遍历列表if inx1 == 0:num_row = len(val)  # 取第一列的列数作为列数if num_row < 3:  # 列数少于3,也认为不是里程碑内容,直接返回输入框内容table_text = milestone_excelreturn table_textfor inx2, val2 in enumerate(val):  # 遍历列表的子项,也是列表if inx1 == 0:  # 第一行:粗体,颜色亮蓝val2 = "<th bgcolor=#87CEFA>" + val2 + "</th>"elif inx1 != 0 and inx2 == 0:  # 第二行开始,第一列if inx1 == 1 or val2 != '':  # 第二行 或者 第一列有值,满足一个,准备吞并n_num = get_n_num(list_n_num, inx1)val2 = "<td rowspan=" + str(n_num) + ">" + val2 + "</th>"else:val2 = "<td>" + val2 + "</td>"td_line = td_line + val2  # 一行的值num = num_row - len(val)  # 存最后一行的列数与第一行的差if num != 0:  # 列数不等于第一列的话,自动补齐空格td_line = td_line + "<td></td>" * numtd_line = "<tr>" + td_line + "</tr>"table_text = table_text + td_linetd_line = ''table_text = "<pre>" + table_text + "</table><br/></pre>"return table_text# 控制系统配置区域控件的展示与隐藏[未实现]
def display_control(window, data):try:if window[event].get_text() == '︽':# 折叠window[event].Update(text='︾')window['text1'].update(visible=False)window['smtp_server'].update(visible=False)window['text2'].update(visible=False)window['email_user'].update(visible=False)window['text3'].update(visible=False)window['email_passwd'].update(visible=False)# window['frame1'].update(value=layout_factor)else:# 展开window[event].Update(text='︽')window['text1'].update(visible=True)window['smtp_server'].update(visible=True)window['text2'].update(visible=True)window['email_user'].update(visible=True)window['text3'].update(visible=True)window['email_passwd'].update(visible=True)except:pass# 将界面字段值写入配置文件
def save_data(values, list_value_conf, flag_UpdateConf=False):""":param values: tool界面的值,类dict:param list_value_conf: 配置文件内实时内容键值对,list:param flag_UpdateConf: 是否更新配置文件标志,默认False"""try:data_conf = copy.deepcopy(list_value_conf)move = dict.fromkeys((ord(c) for c in u"\xa0\n\t"))for key in values:values[key] = str(values[key]).translate(move)values[key] = unicodedata.normalize('NFKC', str(values[key]))if key in ['b1', 'b2']:values[key] = str(values[key])if key not in TAPD_list:continueelif values[key] == data_conf[key]:  # 界面内容vals2与配置文件内容list_value_conf一致continueelse:if key == 'milestone_excel':  # 处理存储ini后空格制表符差异问题values[key] = values['milestone_excel'].replace("   ", "_toolflag_")# config.set("TAPD", key, str(values[key]))  # 写入配置文件unistr = unicodedata.normalize('NFKC', str(values[key]))config.set("TAPD", key, unistr)  # 写入配置文件data_conf[key] = unistrflag_UpdateConf = Trueif flag_UpdateConf:with open(path, 'w') as f1:config.write(f1)print("[%s]界面信息已成功保存,当前数据在工具启动时可自动恢复。" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))except UnicodeError as error:  # 编码异常print(f"[7]当前界面信息中包含特殊符号,如'✓✉☏☞',请处理后【重新操作】!\n异常信息为:{error}")# raise errorexcept Exception as error:print(f"将界面字段值写入配置文件异常,异常信息为:{error}")else:  # 无异常,更新list_value_conf# global list_value_conflist_value_conf = data_conf# 预览里程碑截图
def show_figure(path_milestone):"""预览里程碑截图"""try:if os.path.exists(path_milestone):# import matplotlib# matplotlib.use('tkagg')# import matplotlib.pyplot as plt#fig = plt.figure('里程碑进度图片')# plt.title('里程碑进度图片')ax = fig.add_axes([0, 0, 1, 1])ax.axis('off')ax.imshow(plt.imread(path_milestone), aspect='equal')# Tk specific!fig.canvas.toolbar.pack_forget()# plt.axis('off')  # 关掉坐标轴为 off# plt.ion()  # 开启interactive modeplt.show()plt.close()# from PIL import Image# plt.axis('off')  # 关掉坐标轴为 off# img = Image.open(path_milestone)# plt.ion()  # 开启interactive mode# plt.figure('里程碑进度图片')  # 创建图表 里程碑进度图片# plt.imshow(img)# plt.show()else:sg.popup("不存在【里程碑进度图片】\n请先贴图", title='提示')except Exception as error:if os.path.exists(path_milestone):os.remove(path_milestone)print("查看【里程碑进度图片】异常,错误信息:", error)# 定时器结果通知
def timer_result_mail(smtp_server, email_user, email_passwd, flag):"""定时器发邮件失败了,通知发件人,flag:0成1败"""if flag == 0:flag = "成功"else:flag = "失败"try:  # 发邮件server = smtplib.SMTP(smtp_server, 25)  # 连接发件人邮箱的SMTP服务器,端口是25if flag == "成功":msg = MIMEText(f'【TAPD定时邮件结果】{flag}!', 'plain', 'utf-8')else:msg = MIMEText(f'【TAPD定时邮件结果】{flag}!电脑可能已断网,请及时处理重新发送邮件!', 'plain', 'utf-8')# msg = MIMEText(f'【TAPD定时邮件结果】{flag}!请及时处理!', 'plain', 'utf-8')msg['From'] = formataddr((f"定时日报结果通知>{flag}", email_user))  # 括号里的对应发件人邮箱昵称、发件人邮箱账号msg['To'] = formataddr(("收件人", email_user))  # 括号里的对应收件人邮箱昵称、收件人邮箱账号msg['Subject'] = f"【TAPD定时邮件结果】{flag}!"  # 邮件主题server.login(email_user, email_passwd)  # 括号中对应的是发件人邮箱账号、邮箱密码server.sendmail(email_user, [email_user, ], msg.as_string())  # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件# print('\n"定时器结果"通知成功,请检查邮箱:【{0}】收件箱访问'.format(email_user))server.quit()  # 关闭连接except Exception as err:print('发送"定时器结果通知"邮件失败:{0}\n如果网络正常,请确认邮件账号密码是否正确'.format(err))return False# 更新、校验测试截至时间
def get_send_date(timer=False, set_end=0, start_time=None, end_time=None):"""根据当前时间、是否启用定时器以及定时器定时时长得到邮件实际发送日期timer:boolset_end:intstart_time:测试开始时间 yyyy-mm-ddend_time:测试结束日期 yyyy-mm-ddreturn: end_time"""if timer:  # 启用了定时器time_now = int(set_end / 60) + int(datetime.datetime.now().strftime('%H')) + 1  # 计算真实的发送时间else:time_now = int(datetime.datetime.now().strftime('%H'))  # 当前时# time_now = int(datetime.datetime.now().strftime('%H'))  # 当前时today = datetime.date.today()  # 今天next_day = end_time + datetime.timedelta(days=int(1))  # 下一日next_workday = tommorow(1, end_time=end_time)  # 下一工作日last_workday = tommorow(-1, end_time=end_time)  # 上一工作日if int(time_now) < 24 and end_time == today:  # 今天发if is_workday(end_time):  # 今天是工作日if 6 <= int(time_now) < 12:  # 上午if sg.popup(f"日报发送时辰约为【上午{time_now}点】,可能暂无缺陷数据;\n"f"是否更改【测试截止时间】为上一工作日\n取【{last_workday}】的数据发送日报?",custom_text=(f'{last_workday}', '    NO    '),title='确认') == f'{last_workday}':  # 中午前 # 工作日end_time = last_workday  # 上一工作日elif int(time_now) < 6:  # 凌晨if sg.popup(f"日报发送时辰约为【凌晨{time_now}点】,可能暂无缺陷数据;\n"f"是否更改【测试截止时间】为上一工作日\n取【{last_workday}】的数据发送日报?",custom_text=(f'{last_workday}', '    NO    '),title='确认') == f'{last_workday}':  # 中午前 # 工作日end_time = last_workday  # 上一工作日else:  # 今天是休息日if sg.popup(f"今天为休息日,注意休息;\n是否更改【测试截止时间】为上一工作日\n取【{last_workday}】的数据发送日报?\n",custom_text=(f'{last_workday}', '    NO    '),title='确认') == f'{last_workday}':  # 休息日end_time = last_workday  # 上一工作日elif 24 <= int(time_now) <= 48 and end_time == today:  # 明天发if is_workday(next_day):  # 明天if sg.popup(f"日报发送日期预计为工作日:【{next_day}日{time_now % 24}点】;\n"f"是否更改【测试截止时间】为下一工作日\n取【{next_workday}】的数据发送日报?\n",custom_text=(f'{next_workday}', '    NO    '),title='确认') == f'{next_workday}':  # 真实发送时间到了下一天的下午24+12 # 下一天是工作日end_time = next_workday  # 取下一工作日elif sg.popup(f"日报发送日期预计为【{next_day}】休息日;\n"f"请选择其中一天作为【测试截止时间】\n将取其数据发送日报\n", custom_text=(f'今天[{str(end_time)}]', f'明天[{str(next_day)}]'),title='确认') == f'明天[{str(next_day)}]':  # 真实发送时间到了下一天的下午24+12 # 下一天不是工作日end_time = next_dayelif int(time_now) > 48 and end_time == today:  # 定时更久了,明天以后发daynum = time_now // 24 + 1send_time = end_time + datetime.timedelta(days=int(daynum))  # 发送日sg.popup(f"日报预计发送日期预计为{send_time}\n请选择起始日期{start_time}与预计发送日期{send_time}之间的日期\n作为【测试截止日期】", title='确认')Flag = Truewhile Flag:date = sg.popup_get_date(close_when_chosen=True, month_names=month_names, start_day=15)text = str(date[2]) + '-' + str(date[0]) + '-' + str(date[1])end_time = datetime.date(*map(int, text.split('-')))if start_time <= end_time <= send_time:Flag = Falseelse:sg.popup(f"只能选择起始日期{start_time}与预计发送日期{send_time}之间的日期\n作为【测试截止日期】\n请重新选择!", title='确认')if end_time >= start_time:  # 确保结束日期大于开始日期的前提下,更新结束时间为邮件实际发送日期if end_time != datetime.date.today():print(f"本次日报发送您选取了自【{start_time}】截至【{end_time}】的数据,请注意> > >\n")return str(end_time)else:sg.popup("测试结束日期不能早于测试开始日期", title='确认')return "测试结束日期不能早于测试开始日期"# 得到一个应景诗词token
def _get_verse_token(verse_token):try:token_url = "https://v2.jinrishici.com/token"res = requests.get(url=token_url)assert res.status_code == 200token = res.json().get("data")if token:write_conf(path, "SET", 'verse_token', token)else:write_conf(path, "SET", 'verse_token')return tokenexcept Exception as error:# print("异常信息", error)return False# 得到一句应景诗词
def _get_a_verse(token):try:verse_url = "https://v2.jinrishici.com/sentence"headers = {"X-User-Token": token,# "user-agent": Faker().chrome(version_to=95)}ress = requests.get(url=verse_url, headers=headers)status = ress.json().get("status")assert ress.status_code == 200if status == "success":data = ress.json().get("data").get("content")return dataelse:return Falseexcept Exception as error:# print("异常信息2", error)return False# 判断token,返回诗一行
def get_verse(verse_token, flag=None, tag='forgo'):# flag: 为0返回一句诗,为1返回带格式提示语# forgo:放弃try:data = _get_a_verse(verse_token)  # 第一次获取诗句if not data:  # 您携带的Token不合法,请重新从指定接口签发  # 空/无效token = _get_verse_token(verse_token)time.sleep(1)data = _get_a_verse(token)  # 更新token,第二次获取诗句write_conf(path, "SET", 'verse_token', token)  # token写配置文件 # path是全局变量if data:verse = dataelse:verse = "中国企业,全球梦想"except:verse = "中国企业,全球梦想."if flag:num = int(abs((131 - 3.4375 * len(verse)) // 2 - 11))verse = '-' * num + f'***{tag}***' + '<  ' + verse + '>' + '-' * numreturn versereturn verse# 增加配置文件中配置项的方法
def add_config(path):# 专门用来增加配置文件中配置项的方法,增量增加,不影响老配置add_option(path, "SET", 'verse_token', "")  # 增加配置项:SET:verse_token  # 2021-11-26# 彩蛋事件
def event_LifeEgg(event):if event == '【镇山的虎】':  # 游戏url_yx = ['https://cn.puzzle-loop.com/', 'https://cn.puzzle-sudoku.com/', 'https://cn.puzzle-binairo.com/','https://iogames.space/', 'https://www.yikm.net/', 'https://poki.com/', 'https://www.crazygames.com/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])if event == '【远见的鹰】':  # 小说、漫画、动漫url_yx = ['http://www.iewoai.com/', 'https://www.qianduanmei.com/', 'https://www.kgbook.com/','https://tel.dm5.com/','https://new.shuge.org/', 'http://www.silisili.in/', 'Http://www.nicotv.me/','Https://www.agefans.cc/','Https://www.kanman.com/', 'Http://www.dm5.com/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])if event == '【忠诚的狗】':  # 黑科技url_yx = ['https://essay.1ts.fun/', 'https://koutu.gaoding.com/', 'http://www.ixiqi.com/','https://www.67tool.com/','https://carrotchou.lanzoux.com/b0gwopzc','https://www.wechatsync.com/?utm_source=xinquji&utm_source=xinquji#install','https://www.ghxi.com/', 'https://lanzoui.com/ivBxCmg0xyf', 'https://bigjpg.com/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])if event == '【善战的狼】':  # IT测试相关url_yx = ['https://chrome.zzzmh.cn/#/index', 'https://www.toolfk.com/','https://smalltool.github.io/2020/06/10/soft106/','https://www.canva.cn/?display-com-option=true', 'https://testerhome.com/columns/sylan215','https://www.luogu.com.cn/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])if event == '【害群的马】':  # 官网url_yx = ['http://www.ztccloud.com.cn/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])if event == '【盛饭的桶】':  # 外卖url_yx = ['https://waimai.meituan.com/', 'https://www.ele.me/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])if event == '【卸载工具】':if sg.popup_yes_no('是否确认卸载此工具?', title='') == 'Yes':if os.path.exists(path_pic):try:os.removedirs(path_pic)sg.popup(f"清理完成\n请退出当前程序,手动删除【当前工具.exe】文件\n即可完成卸载!", title='')except:sg.popup(f"删除失败\n可手动卸载:\n1、退出后删除当前工具.exe文件;\n2、删除此文件夹:\n{path_pic}", title='')print(f"删除失败\n可手动卸载:\n1、退出后删除当前工具.exe文件;\n2、删除此文件夹:\n{path_pic}")else:print(get_verse(verse_token, flag=True, tag='forgo'))if event == '【搅屎的棍】':if sg.popup_yes_no('还打算继续加班吗?\n还卷吗?', title='') == 'Yes':sg.popup(f"啊朋友再见吧,再见吧,再见吧!", title='回头是岸')url_yx = ['http://csga.changsha.gov.cn/']webbrowser.open(url_yx[random.randint(0, len(url_yx)) - 1])else:sg.popup("          小小傻猪了不起,天天睡到十点起。              \n""          餐餐五碗才垫底,体重没有谁敢比。\n""          没心没肺厚脸皮,谁人看见都妒忌。\n""          要问小猪在哪里,正在划水看消息。\n", title="哈哈哈...")if event == '你的队友:' or event == '而你:':print(get_verse(verse_token))if __name__ == '__main__':im = Nonetool_vision = "2022_5_31"today = datetime.datetime.today().strftime('%Y-%m-%d')  # 获取今日日期:str# 使用协程检查网络g = gevent.spawn(checkNet_process)g.join()# config = configparser.ConfigParser()  # 存在百分号问题,故放弃,使用基层读取方法RawConfigParserconfig = configparser.RawConfigParser()path_pic = rf'{os.getenv("APPDATA")}' + '\\tapdconf' + '\\'  # 应用文件路径path = path_pic + rf'tapdconf_{tool_vision}.ini'  # 配置文件路径path_milestone = path_pic + rf'里程碑截图.jpg'  # 里程碑截图存储路径TAPD_list = ['cookie', 'url_tapd', 'id', 'start_time', 'smtp_server', 'email_user', 'email_passwd', 'iteration_id','os1','release_id', 'testphase', 'mail_title', 'b2', 'b1', 'addressee', 'mail_cc', 'daily_text','milestone_address', 'milestone_excel', 'manpower_today', 'test_situation','tomorrow_plans', 'list_os', 'list_id', 'list_iteration', 'list_testphase', 'list_release_id','win2_flag']  # 顺序敏感a2 = read_conf(path, TAPD_list)  # 读取配置文件tapdconf.ini的数据layout = tool_ui(a2)  # 加载工具的布局window_name = f"TAPD BUG REPORTS"window = sg.Window(window_name, layout, finalize=True, enable_close_attempted_event=True)  # 加载布局生成窗口"""增加配置项"""add_config(path)# verse_token校验verse_token = get_option(path, "SET", 'verse_token')  # 读verse_tokenif not get_verse(verse_token):  # verse_token不存在或者无效就重读verse_tokenverse_token = get_option(path, "SET", 'verse_token')flag_UpdateConf = False  # 是否更新配置文件的标志old_id = a2["id"]  # 获取界面初始id的值old_iteration_id = a2["iteration_id"]  # 获取界面初始迭代版本id的值old_release_id = a2["release_id"]  # 获取界面初始发布计划的值id_name = a2['list_id'].split('_toolflag_')  # 初始id列表iteration_update = a2['list_iteration'].split('_toolflag_')  # 初始迭代版本id列表release_update = a2['list_release_id'].split('_toolflag_')  # 初始发布计划列表# 预设TAPD缺陷显示字段status_tool = 'id;closed;title;reject_time;reopen_time;severity;priority;status;current_owner;reporter;created;fixer;resolved;de;closer;custom_field_four;'# 窗口二,适应自定义URLwin2_active = False  # win2活跃标志window_active = False  # window活跃标志timer = False  # 是否定时标志set_end = 0  # 邮件定时(min)时长默认零while True:window_active = True  # window活跃标志event, value = window.read(300)for k, v in value.items():  # 值去空if isinstance(k, str):passelse:breakif k in "cookie,id,start_time,smtp_server,email_user,email_passwd,iteration_id,title,os1,testphase,addressee,mail_cc":value[k] = v.strip()cookie = value['cookie'].replace("_toolflag_", "%")if event == '清空':# 点击清空按钮# 1、清空可能会变化的字段信息(没做)# 2、清空日志栏window['debug_result'].Update(value='')passelif event == 'collapse_control':# 界面折叠功能,未实现,先把按钮注释了if window[event].get_text() == '︽':# 折叠window[event].Update(text='︾')window['frame1'].update(visible=False)else:window[event].Update(text='︾')window['frame1'].update(visible=True)elif (event == 'url_method' and not win2_active) or (a2["win2_flag"] == "Y"):  # win2退出的,直接进win2# 切换界面,另一种查询方式win2_active = Truewindow.Hide()win2_flag = "Y"write_conf(path, "TAPD", 'win2_flag', win2_flag)# window.close()a2 = read_conf(path, TAPD_list)  # 读取配置文件tapdconf.ini的数据layout2 = tool_ui2(a2)  # 加载工具的布局win2 = sg.Window('CUSTOM REQUEST REPORTS', layout2, finalize=True, enable_close_attempted_event=True)while True:ev2, vals2 = win2.read(300)if ev2 == '执行':cookie = vals2['cookie'].replace("_toolflag_", "%")if len(cookie) == 0:sg.popup("[TAPD_Cookie]不能为空", title='')continueurl_tapd = vals2['url_tapd']if len(url_tapd) == 0:sg.popup("[获取数据URL]不能为空", title='提示')continuesmtp_server = vals2['smtp_server']  # 默认为腾讯企业邮箱服务器地址,如果用的其他邮箱请更新smtp服务器地址if len(smtp_server) == 0:sg.popup("[邮件服务器地址]不能为空", title='提示')continueemail_user = vals2['email_user']  # 邮件账号if len(email_user) == 0:sg.popup("[邮件服务器账号]不能为空", title='提示')continueif '@' not in email_user:sg.popup("[邮件服务器账号]格式错误", title='提示')continueemail_passwd = vals2['email_passwd']  # 邮件密码if len(email_passwd) == 0:sg.popup("[邮件服务器密码]不能为空", title='提示')continueb2 = '是' if str(vals2['b2']) == 'True' else ''  # 填"是"则包含节假日(节假日指非工作日)# 邮件收件人addressee = vals2['addressee'].replace(',', ',')search_mail = re.findall('@[^,]*@', addressee)search_comma = re.findall(',[^@]*,', addressee)if addressee != '' and '@' not in addressee:sg.popup("[邮件收件人]格式错误", title='提示')continueif len(search_mail) != 0 or len(search_comma) != 0:  # 判断@与,是否交替出现sg.popup("[邮件收件人]校验错误!\n请确认每个邮箱之间都使用单个逗号隔开", title='提示')continuestart_time = vals2["start_time"]  # 测试开始时间if date_check(start_time):continue# 邮件抄送人mail_cc = vals2['mail_cc'].replace(',', ',')search_mail = re.findall('@[^,]*@', mail_cc)search_comma = re.findall(',[^@]*,', mail_cc)if mail_cc != '' and '@' not in mail_cc:sg.popup("[邮件抄送人]格式错误", title='提示')continueif len(search_mail) != 0 or len(search_comma) != 0:  # 判断@与,是否交替出现sg.popup("[邮件抄送人]校验错误!\n请确认每个邮箱之间都使用单个逗号隔开", title='提示')continue# 保存界面数据save_data(vals2, a2, flag_UpdateConf)# 判断邮件发给谁if len(vals2['addressee']) != 0:  # 如收件人列表为空,则发给自己list_addressee = addressee.split(',')  # 去空、去中文逗号,以英文逗号隔开处理成列表list_addressee = list(set(list_addressee))  # list去重if len(vals2['mail_cc']) != 0:  # 如抄送人有值list_mail_cc = mail_cc.split(',')list_mail_cc = list(set(list_mail_cc))  # list去重else:list_mail_cc = [vals2['email_user']]else:list_addressee = [vals2['email_user']]list_mail_cc = [vals2['email_user']]print("[邮件收件人]为空,此邮件将直接发给【%s】\n" % vals2["email_user"])timer = win2['timer'].get()  # 是否定时发送邮件set_end = int(float(vals2["timer_slider"]))  # 邮件定时(min)时长start_time_int = datetime.date(*map(int, start_time.split("-")))end_time = vals2["end_time"]  # 结束时间【可优化成配置】end_time_int = NoneFlag = Trueerror_times = 0while Flag:try:end_time_int = datetime.date(*map(int, end_time.split("-")))if end_time_int < start_time_int:error_times = error_times + 1sg.popup(f"【测试截止日期】不能小于【测试开始日期】\n请使用控件选择【测试截止日期】后\n重新执行", title='确认')date = sg.popup_get_date(close_when_chosen=True, month_names=month_names, start_day=15)end_time = str(date[2]) + '-' + str(date[0]) + '-' + str(date[1])win2['end_time'].Update(value=end_time)  # 更新到界面else:Flag = Falseexcept:error_times = error_times + 1sg.popup(f"【测试截止日期】格式填写错误\n请使用控件选择【测试截止日期】后\n重新执行", title='确认')date = sg.popup_get_date(close_when_chosen=True, month_names=month_names, start_day=15)end_time = str(date[2]) + '-' + str(date[0]) + '-' + str(date[1])win2['end_time'].Update(value=end_time)  # 更新到界面if error_times > 0:  # 有过错误,停下来continueend_time_int = datetime.date(*map(int, end_time.split("-")))# 更新end_time为邮件实际发送日期 # 全局变量  # g/et_date()、analyze_datas()、send_mail()、end_timelist_date = []list_date_Source = "chinese_calendar"  # 时间轴日期列表取值方式,默认chinese_calendar库取 # 页面全局变量try:  # 检查chinese_calendar库是否适用is_workday(end_time_int)except NotImplementedError:# sg.popup("chinese_calendar库太旧,下面自动更新chinese_calendar库", title='提示')# os.system("pip install --disable-pip-version-check --upgrade chinese_calendar")list_date_Source = "by_hand"  # 已不可能chinese_calendarstr_date = sg.popup_get_text(f"测试时间区间跨年了,chinese_calendar库不能自动获取到所需年份对应的版本.\n所以只能手动处理\n"f"{'-' * 55}解决方式1{'-' * 55}""\n手动输入【测试起止日期】区间内的所有你需要统计数据的日期到下面输入框\n格式为[yyyy-mm-dd],次序为[从早到晚],日期间[逗号隔开]\n""工具将取你填写的第一个日期作为【测试开始时间】,最后一个日期为【测试结束时间】\n完成后点击Ok."f"\n{'-' * 55}解决方式2{'-' * 55}""\n点击Cancel或者右上角X退出,联系管理员重新打包.\n", title='提示:遇上麻烦事了!!!请仔细阅读',size=(70, 1))if str_date:  # 有值list_date = str_date.replace(",", ",").split(',')# 列表去重、去空 # 校验各项日期格式if len(list_date):list_date2 = list_datelist_date = list(set(list_date))  # 去重list_date.sort(key=list_date2.index)r = re.compile('^\d{4}-\d{2}-\d{2}')  # 正则规则,yyyy-mm-ddlist_date = [i for i in list_date if i != '' and r.match(i)]  # 去空, 去非日期格式if len(list_date):if sg.popup_yes_no(f"您手动填写的日期列表整理后为:\n{','.join(list_date)}\n{'-' * 60}\n1、日期"f"(除休息日)如不连续影响画图\n2、请确认列表中日期格式都正确\n3、日期列表与URL中的"f"日期范围需一致\n{'-' * 60}\n错误的话点击[No]中止发送",title='确认') == "No":print(f"手动填写的日期列表为\n{','.join(list_date)}\n已中止发送,可以修改正确后复制粘贴再次尝试")continue  # 中止# 继续else:sg.popup(f"您手动填写的日期列表格式完全不正确\n请重新操作", title='提示')continueelse:print(get_verse(verse_token, flag=1, tag='forgo'))  # 中止提示continueexcept:sg.popup("测试时间区间跨年了,chinese_calendar库不适用当前年份,\n请联系管理员重新打包", title='提示')continueif list_date_Source == "chinese_calendar":end_time = get_send_date(timer, set_end, start_time_int, end_time_int)  # 更新结束时间if end_time == "测试结束日期不能早于测试开始日期":continueelse:if len(list_date) > 0:start_time = list_date[0]end_time = list_date[-1]print(f"本次日报发送您选取了【{','.join(list_date)}】作为日期数据,如果日期(除休息日)不连续数据会不准确,请注意> > >\n")try:  # 生成时间轴if list_date_Source == "chinese_calendar":list_date = get_date(start_time, end_time, b2)  # list_date:趋势图时间轴except NotImplementedError:sg.popup("chinese_calendar库不适用当前年份,\n请联系管理员", title='提示')continue# if data_tapd is False:#     sg.popup("[获取数据URL]不可用,\n请确保此URL是复制的触发了TAPD过滤器的URL。\n若有疑问请联系管理员", title='提示')#     continuetry:view_id, conf_id, id, bugs_numb = get_bugs_list_by_url(cookie, url_tapd, page=1, perpage=100,flag="info")  # wqflagexcept:continue"""获取现有TAPD缺陷显示字段"""status_old = get_show_fields(cookie, id, view_id)if status_old is False:continue"""获取BUG总数"""print("\n----------------------------------------   执行开始[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")if bugs_numb == 0:button_return = sg.popup_ok_cancel("项目下当前迭代版本缺陷总数为0,建议先确认。\n是否继续发送邮件?")if button_return == 'Cancel':print(f"当前迭代版本缺陷总数为0,中止执行.<{get_verse(verse_token)}>")print("\n----------------------------------------   中止执行[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")continuetry:"""设置TAPD缺陷显示字段为预设字段"""if edit_show_fields(cookie, id, view_id, status_tool) is False:continue"""得到所有缺陷的字段数据,并记录成插表语句"""list_sql = []  # 记录所有缺陷数据处理后得到的插表sqlif bugs_numb > 200:print(f"一共【{bugs_numb}】条缺陷,生成图片需要一定时间,请稍等\n")else:print(f"一共【{bugs_numb}】条缺陷:\n")for i in range(1, math.ceil(bugs_numb / 100) + 1):data_tapd = get_bugs_list_by_url(cookie, url_tapd, page=i, perpage=100, flag="bugs_list")if data_tapd is False:continuelist_sql = list_sql + data_cleanup(data_tapd)if len(list_sql) == 0 and bugs_numb != 0:sg.popup("解析缺陷数据失败!\n请确认[获取数据URL]是不是基于【缺陷视图-所有的】而创建!\n如果一直无法解决可以切换成工具的日常模式使用。\n若有疑问请联系管理员~",title='提示')continue"""处理里程碑输入框:读取里程碑输入框内容,如果是excel粘贴的,则处理成html中的table标签"""list_milestone_excel = vals2['milestone_excel'].replace("_toolflag_", "    ").split('\n')if len(list_milestone_excel) > 5:  # 判断内容5行以上,当作是里程碑表格处理格式table_text = text_to_table(list_milestone_excel, vals2['milestone_excel'])else:table_text = vals2['milestone_excel']list_vals2 = [vals2['daily_text'], vals2['test_situation'], vals2['tomorrow_plans'],vals2['manpower_today'],vals2['milestone_address'], table_text, vals2['mail_title']]"""整理数据,发邮件"""send_mail(email_user, email_passwd, list_addressee, list_mail_cc, smtp_server,analyze_datas(list_sql, list_date, id), list_vals2)except Exception as err:if isinstance(err, UnicodeEncodeError):print("\n邮件发送失败! [邮件服务器账号/密码/收件人/抄送人]输入了异常字符,比如中文。\n错误信息:", err)else:print("处理缺陷数据出错,请联系管理员。\n[1]错误信息:", err)continuefinally:"""还原TAPD缺陷显示字段"""# visible_feild(status_old, id, cookie)if edit_show_fields(cookie, id, view_id, status_old) is False:passprint("\n----------------------------------------   执行结束[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")elif ev2 == '暂存':# 点击暂存按钮# 将界面上填写的内容存到配置文件# 更新配置内容save_data(vals2, a2, flag_UpdateConf)elif ev2 == '贴图':# 点击贴图按钮# 1、读取剪切板截图内容,保存成图片# 2、插入邮件模板的里程碑进度图的位置im = ImageGrab.grabclipboard()try:im.save(path_milestone)print("【里程碑进度截图】插入成功\n")except Exception as error:# 再操作说明想改,删除历史图片if os.path.exists(path_milestone):os.remove(path_milestone)sg.popup("该操作原理是读取剪切板\n请先【截取里程碑进度】的图片再操作", title='错误')elif ev2 == '预览':# 点击预览按钮# 1、预览里程碑截图show_figure(path_milestone)elif ev2 == 'url_method':win2.hide()win2_active = Falsewin2_flag = "N"write_conf(path, "TAPD", 'win2_flag', win2_flag)# window.close()a2 = read_conf(path, TAPD_list)  # 读取配置文件tapdconf.ini的数据layout = tool_ui(a2)  # 加载工具的布局window = sg.Window(window_name, layout, finalize=True,enable_close_attempted_event=True)  # 加载布局生成窗口breakelif ev2 == '源码':if sg.popup_yes_no("是否打开存放源码的博客?", title='确认') == 'Yes':url = 'https://blog.csdn.net/cat_l_over/article/details/120185016'webbrowser.open(url)elif (ev2 in ['【镇山的虎】', '【远见的鹰】', '【忠诚的狗】', '【善战的狼】', '【害群的马】', '【盛饭的桶】', '【卸载工具】', '【搅屎的棍】', '你的队友:','而你:']):event_LifeEgg(ev2)elif (ev2 == sg.WINDOW_CLOSE_ATTEMPTED_EVENT or ev2 == sg.WINDOW_CLOSED) and \sg.popup_yes_no('退出程序?', title='确认') == 'Yes':window_active = Falsewin2_active = Falsewin2_flag = "Y"write_conf(path, "TAPD", 'win2_flag', win2_flag)breakwin2.close()if window_active is False:breakelif event == '暂存':# 点击暂存按钮# 将界面上填写的内容存到配置文件# 更新配置内容save_data(value, a2, flag_UpdateConf)elif event == '贴图':# 点击贴图按钮# 1、读取剪切板截图内容,保存成图片# 2、插入邮件模板的里程碑进度图的位置im = ImageGrab.grabclipboard()try:im.save(path_milestone)print("【里程碑进度截图】插入成功\n")except Exception as error:# 再操作说明想改,删除历史图片if os.path.exists(path_milestone):os.remove(path_milestone)sg.popup("该操作原理是读取剪切板\n请先【截取里程碑进度】的图片再操作", title='错误')elif event == '预览':# 点击预览按钮# 1、预览里程碑截图show_figure(path_milestone)elif event == 'GET':# 点击GET按钮# 1、根据cookie更新项目list# 2、list写入配置文件(配置文件需要增加存储动态下拉框的类型[LIST])if len(cookie) == 0:sg.popup("[TAPD_Cookie]不能为空", title='')continueelse:id_name = tapd_id(cookie)  # 获取项目id列表if id_name:window['id'].Update(values=id_name)write_conf(path, "TAPD", 'list_id', '_toolflag_'.join(id_name))else:sg.popup("获取项目列表为空,可选择手动输入[项目]id", title='提示')continueelif event == 'id':# 界面触发id列表选择事件,立刻恢复下拉列表的内容完整# 刷新获取迭代版本# 跳过项目id字段更新事件old_id = value['id']  # 触发了选择事件,就让更新的判断失效,以不再进更新window['id'].Update(value=value["id"], values=id_name)if len(cookie) == 0:sg.popup("[TAPD_Cookie]不能为空", title='')continue# 迭代版本idtry:iteration_update = get_options(cookie, value['id'][-8:])  # wqflagif len(iteration_update) > 0:window['iteration_id'].update(values=iteration_update)print("成功获取所选项目下的[迭代版本]\n")else:print("获取当前项目[迭代版本]失败,请检查[TAPD_Cookie]、[项目]字段,或手动填写迭代版本id")except Exception as err:if loglevel == "debug":raise errsg.popup("使用[项目]更新[迭代版本]失败!\n请检查[TAPD_Cookie]与[项目]是否匹配", title='提示')continue# 发布计划idtry:release_update = get_release_id(cookie, value['id'][-8:])  # 带有发布计划信息的html文件if release_update is False:continueif len(release_update) > 0:window['release_id'].update(values=release_update)print("成功获取所选项目下的[发布计划]\n")# 预备写配置文件config.read(path)iteration_update_ = unicodedata.normalize('NFKC', '_toolflag_'.join(iteration_update))release_id_update_ = unicodedata.normalize('NFKC', '_toolflag_'.join(release_update))config.set("TAPD", 'list_iteration', iteration_update_)  # 迭代版本更新至配置文件config.set("TAPD", 'list_release_id', release_id_update_)  # 发布计划更新至配置文件flag_UpdateConf = Trueelse:print("获取当前项目[发布计划]为0,如异常请检查[TAPD_Cookie]、[项目]字段,或手动填写发布计划id")except Exception as err:if loglevel == "debug":raise errcontinueelif value['id'] != old_id:# 根据项目id字段输入框的内容实时筛选项目下拉列表old_id = value["id"]window['id'].Update(value=value["id"],values=search_result(id_name, value['id']))elif event == 'iteration_id':# 界面触发迭代版本id选择事件,还原迭代版本下拉列表内容window['iteration_id'].Update(value=value["iteration_id"],values=iteration_update)old_iteration_id = value['iteration_id']  # 触发了选择事件,就让更新事件失效,不再进更新elif value['iteration_id'] != old_iteration_id:# 根据迭代版本id字段输入框的内容实时筛选迭代版本下拉列表old_iteration_id = value["iteration_id"]window['iteration_id'].Update(value=value["iteration_id"],values=search_result(iteration_update, value['iteration_id']))elif event == 'release_id':# 界面触发迭代版本id选择事件,还原迭代版本下拉列表内容window['release_id'].Update(value=value["release_id"], values=release_update)old_release_id = value['release_id']  # 触发了选择事件,就让更新事件失效,不再进更新elif value['release_id'] != old_release_id:# 根据发布计划id字段输入框的内容实时筛选迭代版本下拉列表old_release_id = value["release_id"]window['release_id'].Update(value=value["release_id"],values=search_result(release_update, value['release_id']))elif event == '执行':# 获取界面信息if len(cookie) == 0:sg.popup("[TAPD_Cookie]不能为空", title='')continueid = value['id'].split('--')[-1]  # 项目IDif input_check(id):  # 检查项目id字段格式sg.popup("[项目]字段校验错误!\n如果是手动输入只支持输入id,请确认", title='提示')continuestart_time = value["start_time"]  # 测试开始时间if date_check(start_time):continue# 以下验证必填项smtp_server = value['smtp_server']  # 默认为腾讯企业邮箱服务器地址,如果用的其他邮箱请更新smtp服务器地址if len(smtp_server) == 0:sg.popup("[邮件服务器地址]不能为空", title='提示')continueemail_user = value['email_user']  # 邮件账号if len(email_user) == 0:sg.popup("[邮件服务器账号]不能为空", title='提示')continueif '@' not in email_user:sg.popup("[邮件服务器账号]格式错误", title='提示')continueemail_passwd = value['email_passwd']  # 邮件密码if len(email_passwd) == 0:sg.popup("[邮件服务器密码]不能为空", title='提示')continueprint("\n----------------------------------------   执行开始[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")# 更新配置内容save_data(value, a2, flag_UpdateConf)"""以下过滤规则非必填"""# 邮件收件人addressee = value['addressee'].replace(',', ',')search_mail = re.findall('@[^,]*@', addressee)search_comma = re.findall(',[^@]*,', addressee)if addressee != '' and '@' not in addressee:sg.popup("[邮件收件人]格式错误", title='提示')continueif len(search_mail) != 0 or len(search_comma) != 0:  # 判断@与,是否交替出现sg.popup("[邮件收件人]校验错误!\n请确认每个邮箱之间都使用单个逗号隔开", title='提示')continue# 邮件抄送人mail_cc = value['mail_cc'].replace(',', ',')search_mail = re.findall('@[^,]*@', mail_cc)search_comma = re.findall(',[^@]*,', mail_cc)if mail_cc != '' and '@' not in mail_cc:sg.popup("[邮件抄送人]格式错误", title='提示')continueif len(search_mail) != 0 or len(search_comma) != 0:  # 判断@与,是否交替出现sg.popup("[邮件抄送人]校验错误!\n请确认每个邮箱之间都使用单个逗号隔开", title='提示')continue# 发布计划release_id = value['release_id'].split('--')[-1]if release_id != '':if input_check(release_id):  # 检查发布计划字段格式sg.popup("[发布计划]字段校验错误!\n如果是手动输入只支持输入发布计划的id,请确认", title='提示')continue# 迭代版本 (必须与项目id匹配),示例:2.1.7.2版本-收入结算平台-20210910iteration_id = value['iteration_id'].split('--')[-1]if iteration_id != '':if input_check(iteration_id):  # 检查发布计划字段格式sg.popup("[迭代版本]字段校验错误!\n如果是手动输入只支持输入迭代版本的id,请确认", title='提示')continueif iteration_id == '' and release_id == '':  # 都为空会查出大数据量缺陷,做出提示if sg.popup_yes_no("[迭代版本]、[发布计划]均为空\n将查询出项目下所有分支的缺陷\n是否继续?", title='确认') == 'No':print("\n----------------------------------------   中止执行[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")continueos1 = value['os1']  # 测试环境/UAT环境或者其他什么环境# title = value['title']  # 标题过滤title = ''  # 标题过滤testphase = value['testphase']  # 测试阶段b2 = '是' if str(value['b2']) == 'True' else ''  # 填"是"则包含节假日(节假日指非工作日)# 判断邮件发给谁if len(value['addressee']) != 0:  # 如收件人列表为空,则发给自己list_addressee = addressee.split(',')  # 去空、去中文逗号,以英文逗号隔开处理成列表list_addressee = list(set(list_addressee))  # list去重if len(value['mail_cc']) != 0:  # 如抄送人有值list_mail_cc = mail_cc.split(',')list_mail_cc = list(set(list_mail_cc))  # list去重else:list_mail_cc = [value['email_user']]else:list_addressee = [value['email_user']]list_mail_cc = [value['email_user']]print("[邮件收件人]为空,此邮件将直接发给【%s】\n" % value["email_user"])timer = window['timer'].get()  # 是否定时发送邮件set_end = int(float(value["timer_slider"]))  # 邮件定时(min)时长start_time_int = datetime.date(*map(int, start_time.split("-")))# start_time = value["start_time"]  # 测试开始时间end_time = value["end_time"]  # 结束时间【可优化成配置】end_time_int = NoneFlag = Trueerror_times = 0while Flag:try:end_time_int = datetime.date(*map(int, end_time.split("-")))if end_time_int < start_time_int:error_times = error_times + 1sg.popup(f"【测试截止日期】不能小于【测试开始日期】\n请使用控件选择【测试截止日期】后\n重新执行", title='确认')date = sg.popup_get_date(close_when_chosen=True, month_names=month_names, start_day=15)end_time = str(date[2]) + '-' + str(date[0]) + '-' + str(date[1])window['end_time'].update(value=end_time)  # 更新到界面else:Flag = Falseexcept:error_times = error_times + 1sg.popup(f"【测试截止日期】格式填写错误\n请使用控件选择【测试截止日期】后\n重新执行", title='确认')date = sg.popup_get_date(close_when_chosen=True, month_names=month_names, start_day=15)end_time = str(date[2]) + '-' + str(date[0]) + '-' + str(date[1])window['end_time'].update(value=end_time)  # 更新到界面if error_times > 0:  # 有过错误,停下来continueend_time_int = datetime.date(*map(int, end_time.split("-")))# 更新end_time为邮件实际发送日期 # 全局变量  # g/et_date()、analyze_datas()、send_mail()、end_timelist_date = []list_date_Source = "chinese_calendar"  # 时间轴日期列表取值方式,默认chinese_calendar库取 # 页面全局变量try:  # 检查chinese_calendar库是否适用is_workday(end_time_int)except NotImplementedError:# sg.popup("chinese_calendar库太旧,下面自动更新chinese_calendar库", title='提示')# os.system("pip install --disable-pip-version-check --upgrade chinese_calendar")list_date_Source = "by_hand"  # 已不可能chinese_calendarstr_date = sg.popup_get_text(f"测试时间区间跨年了,chinese_calendar库不能自动获取到所需年份对应的版本.\n所以只能手动处理\n"f"{'-' * 55}解决方式1{'-' * 55}""\n手动输入【测试起止日期】区间内的所有你需要统计数据的日期到下面输入框\n格式为[yyyy-mm-dd],次序为[从早到晚],日期间[逗号隔开]\n""工具将取你填写的第一个日期作为【测试开始时间】,最后一个日期为【测试结束时间】\n完成后点击Ok."f"\n{'-' * 55}解决方式2{'-' * 55}""\n点击Cancel或者右上角X退出,联系管理员重新打包.\n", title='提示:遇上麻烦事了!!!请仔细阅读', size=(70, 1))if str_date:  # 有值list_date = str_date.replace(",", ",").split(',')# 列表去重、去空 # 校验各项日期格式if len(list_date):list_date2 = list_datelist_date = list(set(list_date))  # 去重list_date.sort(key=list_date2.index)r = re.compile('^\d{4}-\d{2}-\d{2}')  # 正则规则,yyyy-mm-ddlist_date = [i for i in list_date if i != '' and r.match(i)]  # 去空, 去非日期格式if len(list_date):  # 去重后再次判断长度if sg.popup_yes_no(f"您手动填写的日期列表整理后为:\n{','.join(list_date)}\n{'-' * 60}\n1、日期(除休息日)如不连续影响画图\n2、请确认列表中日期格式都正确\n3、日期列表与URL中的日期范围需一致\n{'-' * 60}\n错误的话点击[No]中止发送",title='确认') == "No":print(f"手动填写的日期列表为\n{','.join(list_date)}\n已中止发送,可以修改正确后复制粘贴再次尝试")continue  # 中止# 继续else:sg.popup(f"您手动填写的日期列表格式完全不正确\n请重新操作", title='提示')continueelse:print(get_verse(verse_token, flag=1, tag='forgo'))  # 中止发送continueexcept:sg.popup("测试时间区间跨年了,chinese_calendar库不适用当前年份,\n请联系管理员重新打包", title='提示')continueif list_date_Source == "chinese_calendar":end_time = get_send_date(timer, set_end, start_time_int, end_time_int)  # 更新结束时间if end_time == "测试结束日期不能早于测试开始日期":continueelse:if len(list_date) > 0:start_time = list_date[0]end_time = list_date[-1]print(f"本次日报发送您选取了【{','.join(list_date)}】作为日期数据,如果日期(除休息日)不连续数据会不准确,请注意> > >\n")try:  # 生成时间轴if list_date_Source == "chinese_calendar":list_date = get_date(start_time, end_time, b2)  # list_date:趋势图时间轴except NotImplementedError:sg.popup("chinese_calendar库不适用当前年份,\n请联系管理员", title='提示')continue# data_tapd = get_datas(cookie, id, title, start_time, iteration_id, end_time, testphase, release_id, os1)  # 根据迭代版本id请求一次,获取现有TAPD缺陷显示字段以及当前迭代版本BUG总数view_id, conf_id = get_bug_fields_userview_and_list(cookie, id)  # wqflag# status_old = f'{";".join(re.findall(r"data-sort=.(.*?);", data_tapd, re.S))};'  # 获取现有TAPD缺陷显示字段status_old = get_show_fields(cookie, id, view_id)  # 获取现有TAPD缺陷显示字段if status_old is False:continuebugs_numb = int(get_bugs_list(cookie, id, conf_id, start_time, end_time, title, iteration_id, os1, testphase,release_id, flag="total_count"))  # 获取BUG总数if bugs_numb is False:continueif bugs_numb == 0:button_return = sg.popup_ok_cancel("项目下当前迭代版本缺陷总数为0,建议先确认。\n是否继续发送邮件?")if button_return == 'Cancel':print("当前迭代版本缺陷总数为0,中止执行")print("\n----------------------------------------   中止执行[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")continuecontinue_flag = True  # 继续发送邮件的标记if timer:  # 勾上了,定时是在取数据之前print(f'电脑自动睡眠/睡眠会导致断网,请设置电脑,保证倒计时期间电脑不会进入睡眠模式!')print(f'\n定时执行,倒计时:{set_end}分钟...')continue_flag = Falsei = 0  #sg.ChangeLookAndFeel('Black')  # #64778Dsg.SetOptions(element_padding=(0, 0))layout3 = timer_ui()win3 = sg.Window('邮件发送倒计时', layout3, no_titlebar=False, auto_size_buttons=False, keep_on_top=True,grab_anywhere=True)paused = False  # 执行标志start_time = 0  # 记录一次执行开始的时间,srunning_time = 0  # 记录一次执行时间 sleft_time = set_end * 60  # 记录剩余时间 sshow_time = left_time  # 最终用于展示的时间 swhile True:if paused:ev3, vals3 = win3.read(1000)now_time = int(time.time())  # 当前时间,srunning_time = now_time - start_time  # 执行时间,sshow_time = left_time - running_time  # 剩余时间else:ev3, vals3 = win3.read(1000)button_name = win3["开始计时"].get_text()if ev3 == '重新计时':print(f'[{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}]重新计时,倒计时:{set_end}分钟')# 变量统统复位running_time = 0left_time = set_end * 60show_time = left_timepaused = Falsewin3['开始计时'].update(text='开始计时')if window_active is False:window.UnHide()window_active = Truei = 0if ev3 == '开始计时' and button_name == '暂停计时':  # 暂停print(f'[{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}]暂停计时')paused = Falsestart_time = int(time.time())win3[ev3].update(text='开始计时')if window_active is False:window.UnHide()window_active = Truei = 0if ev3 == '开始计时' and button_name == '开始计时':if i == 0:print(f'[{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}]开始计时')window.Hide()window_active = Falsei = 1start_time = int(time.time())  # 开始时间,sleft_time = left_time - running_timepaused = Truewin3[ev3].update(text='暂停计时')if ev3 is None or ev3 == '中止发送':  # 中止发送paused = Falsecontinue_flag = Falsebreakif show_time <= 0 and button_name == '暂停计时':paused = Falseprint(f'[{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}]计时结束\n')win3["开始计时"].update(text='开始计时')continue_flag = Truebreak# --------- Display timer in win3 --------win3['text_time'].update('{:02d}:{:02d}:{:02d}'.format(show_time // 3600, (show_time // 60) % 60, show_time % 60))win3.close()sg.ChangeLookAndFeel('BrownBlue')if window_active is False:window_active = Truewindow.UnHide()if continue_flag is False:print("\n----------------------------------------   中止执行[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")continuetry:"""设置TAPD缺陷显示字段为预设字段"""visible_feild_tmp = edit_show_fields(cookie, id, view_id, status_tool)if visible_feild_tmp is False:continue"""得到所有缺陷的字段数据,并记录成插表语句"""list_sql = []if bugs_numb > 200:print(f"一共【{bugs_numb}】条缺陷,生成图片需要一定时间,请稍等\n")else:print(f"一共【{bugs_numb}】条缺陷:\n")for i in range(1, math.ceil(bugs_numb / 100) + 1):data_tapd = get_bugs_list(cookie, id, conf_id, start_time, end_time, title, iteration_id, os1,testphase,release_id, page=i, perpage=100)if data_tapd is False:continuelist_sql = list_sql + data_cleanup(data_tapd)"""读取里程碑输入框内容,如果是excel粘贴的,则处理成html中的table"""list_milestone_excel = value['milestone_excel'].replace("_toolflag_", " ").split('\n')if len(list_milestone_excel) > 5:  # 设置判断内容有5行以上才当作是里程碑表格处理格式table_text = text_to_table(list_milestone_excel, value['milestone_excel'])else:table_text = value['milestone_excel']list_value = [value['daily_text'], value['test_situation'], value['tomorrow_plans'],value['manpower_today'],value['milestone_address'], table_text, value['mail_title']]result_tool = send_mail(email_user, email_passwd, list_addressee, list_mail_cc, smtp_server,analyze_datas(list_sql, list_date, id), list_value)if result_tool and timer:timer_result_mail(smtp_server, email_user, email_passwd, 0)  # 定时发送成功,发个通知邮件except Exception as err:if loglevel == "debug":raise errif isinstance(err, UnicodeEncodeError):print("\n邮件发送失败! [邮件服务器账号/密码/收件人/抄送人]输入了异常字符,比如中文。\n错误信息:", err)else:print("处理缺陷数据出错,请联系管理员。\n[2]错误信息:", err)continuefinally:"""还原TAPD缺陷显示字段"""edit_show_fields(cookie, id, view_id, status_old)print("\n----------------------------------------   执行结束[",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"]  ---------------------------------------\n")# breakelif event == '源码':if sg.popup_yes_no("是否打开存放源码的博客?", title='确认') == 'Yes':url = 'https://blog.csdn.net/cat_l_over/article/details/120185016'webbrowser.open(url)else:print(f"{get_verse(verse_token, flag=1, tag='forgo')}")continueelif (event in ['【镇山的虎】', '【远见的鹰】', '【忠诚的狗】', '【善战的狼】', '【害群的马】', '【盛饭的桶】', '【卸载工具】', '【搅屎的棍】', '你的队友:','而你:']):event_LifeEgg(event)elif (event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT or event == 'Exit') and sg.popup_yes_no('退出程序?',title='确认') == 'Yes':win2_flag = "N"write_conf(path, "TAPD", 'win2_flag', win2_flag)breakwindow.close()

XY的小伙伴你好_TAPD(2022.7版本)测试日报发送相关推荐

  1. XY的小伙伴你好_FOL(4.1版本)批量提单进支付

    # --*--coding:utf-8--*-- # 有事问刘超 # updatedtime:20220428from urllib.parse import unquote import pyper ...

  2. 【方向盘】升级到IDEA 2022.1版本后,我把Maven Helper卸载了

    本文已被https://yourbatman.cn收录:女娲Knife-Initializr工程可公开访问啦:程序员专用网盘https://wangpan.yourbatman.cn:技术专栏源代码大 ...

  3. 服务器搭建系列之序章:总览,2022最新版本

    服务器搭建系列之1:centos安装docker,docker-compose,开启docker远程部署,2022最新版本 服务器搭建系列之2:centos安装kubernetes(k8s)集群v1. ...

  4. 【npm】node-gpy 内嵌npm版本升级 (解决node-gpy 内嵌 npm版本过低识别不了2022 msvs_version版本问题)

    [npm]node-gpy 内嵌npm版本升级 (解决node-gpy 内嵌 npm版本过低识别不了2022 msvs_version版本问题) 问题 原因 解决 这次运行同学给我们的另一个vue项目 ...

  5. 2022“点点点”测试员如何上岸测试开发岗?附完整学习路线!

    有很多人员会不断问自己,自己到底要不要学测试,或者要不要坚持做测试,测试的职业发展到底怎么样?如果你还在迷茫,在到处找各种大牛问类似的问题,我希望这篇文章,你看完能够结束你的这个烦恼,给你更多的指明方 ...

  6. 2022 . 11 . 26 测试赛解题报告

    2022 . 11 . 26测试赛 [USACO21DEC] Lonely Photo B 题目描述 Farmer John 最近购入了 NNN 头新的奶牛(3≤N≤5×1053 \le N \le ...

  7. Cocos Creator -构建打包 所有版本测试

    Cocos Creator -构建打包 所有版本测试 目前在打包APK时,出现了种种问题,都是因为打包环境操作,所以为了解决所有同行的同惑 ***大菠萝***做了一系列测评 SDK26 SDK27 S ...

  8. android实现直接发短信,android5.0以上版本如何直接发送短信?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 下面的代码在5.0一下版本能够直接发送,但5.0以上就会跳转到系统的短信发送界面,应该怎么修改呢?(我有看到过一个叫"招商致远手机证券" ...

  9. iOS - 使用TestFlight进行App的Beta版本测试

    TestFlight能够让我们很容易在app正式发布之前邀请用户来测试我们的app并且收集反馈信息.为了使用TestFlight,仅仅是需要上传app的beta版本iTunes Connect,然后在 ...

最新文章

  1. StatQuest-对RNA-seq的介绍
  2. Java学习笔记21
  3. WEB数据挖掘(八)——Aperture数据抽取(4):Aperture整体结构
  4. linux 追加多行文件,linux多行文件信息追加
  5. python列表批量 修改_python实现多进程按序号批量修改文件名的方法示例
  6. HDU 6706 huntian oy (欧拉函数 + 杜教筛)
  7. makefile 无法工作_什么是Makefile,它如何工作?
  8. iPhoneXI/XI MAX机模曝光:浴霸式摄像头着实抢眼
  9. Android系列之网络(二)----HTTP请求头与响应头
  10. html+input改变图标,JS Input里添加小图标的两种方法
  11. 美来临公司商业模式分析,公司是不昨的
  12. 跟我一起写大虾网(第0天)
  13. python 双色球 大乐透 5注随机选号
  14. 这篇python正则表达式颠覆你的人生观,详细到让你怀疑人生!
  15. js每日一题(12)
  16. 全球及中国DIN 2353压缩配件行业研究及十四五规划分析报告
  17. 流程表结构设计第一版
  18. django 改端口_django更改默认的runserver端口
  19. Python逆向进阶:Web逆向私单
  20. 华硕服务器设置固态盘启动不了系统盘,华硕uefi引导启动不了系统安装系统安装...

热门文章

  1. android 自定义View绘制电池电量(电池内带数字显示)
  2. ANSYS Workbench打开字体太小解决方案
  3. 查看某一时间段的cpu情况(系统性能)
  4. 微软q#_掌握Microsoft的P#
  5. 北京工商大学计算机考研818真题,2018年北京工商大学818数据结构考研大纲
  6. 服务器用固态硬盘好处
  7. WebStorm配置nodemon
  8. AJPFX讲解java关键词过滤算法
  9. javaSE01_计算机常识_MarkDown语法
  10. windows服务器的配置