From 7de2f5e8e982af5e0127f4871018828cc876d1db Mon Sep 17 00:00:00 2001 From: smileknife Date: Tue, 27 Oct 2020 14:03:28 +0800 Subject: [PATCH] [review_tool]support editing review item Signed-off-by: smileknife --- advisors/gitee.py | 79 +++++++++++++++++++++++++++++----------- advisors/review_tool.py | 81 +++++++++++++++++++++++++++++++++++------ 2 files changed, 127 insertions(+), 33 deletions(-) diff --git a/advisors/gitee.py b/advisors/gitee.py index b8615a17..b6217f5d 100755 --- a/advisors/gitee.py +++ b/advisors/gitee.py @@ -41,7 +41,7 @@ class Gitee(): self.advisor_url = "https://gitee.com/openeuler/openEuler-Advisor/raw/master/" self.time_format = "%Y-%m-%dT%H:%M:%S%z" - def post_gitee(self, url, values, headers=None): + def __post_gitee(self, url, values, headers=None): """ POST into gitee API """ @@ -53,8 +53,24 @@ class Gitee(): result = urllib.request.urlopen(req) return result.read().decode("utf-8") except urllib.error.HTTPError as err: - print("WARNING:" + str(err.code)) - print("WARNING:" + str(err.headers)) + print("ERROR: error occurred.\nerrcode: %d\nreason: %s\nheaders:\n%s" + % (err.code, err.reason, str(err.headers))) + return False + + def __patch_gitee(self, url, values, headers=None): + """ + PATCH method to gitee API + """ + if headers is None: + headers = self.headers.copy() + data = urllib.parse.urlencode(values).encode('utf-8') + req = urllib.request.Request(url=url, data=data, headers=headers, method="PATCH") + try: + result = urllib.request.urlopen(req) + return result.read().decode("utf-8") + except urllib.error.HTTPError as err: + print("ERROR: error occurred.\nerrcode: %d\nreason: %s\nheaders:\n%s" + % (err.code, err.reason, str(err.headers))) return False def fork_repo(self, repo, owner="src-openeuler"): @@ -69,7 +85,7 @@ class Gitee(): #headers["Content-Type"] = "application/json;charset=UTF-8" #headers["HOST"] = "gitee.com" #headers["Accept"] = "*/*" - return self.post_gitee(url, values) + return self.__post_gitee(url, values) def create_issue(self, repo, version, branch): """ @@ -88,7 +104,7 @@ class Gitee(): """ url_template = "https://gitee.com/api/v5/repos/{owner}/{pkg}/collaborators" url = url_template.format(owner=owner, pkg=repo) - return self.get_gitee(url) + return self.__get_gitee(url) def create_pr(self, repo, version, branch, owner="src-openeuler"): """ @@ -112,7 +128,7 @@ class Gitee(): Review carefully before accept this PR. Thanks. Yours openEuler-Advisor.""" - return self.post_gitee(url, values) + return self.__post_gitee(url, values) def create_pr_comment(self, repo, number, body, owner="src-openeuler"): """ @@ -123,9 +139,28 @@ class Gitee(): values = {} values["access_token"] = self.token["access_token"] values["body"] = body - return self.post_gitee(url, values) + return self.__post_gitee(url, values) + + def get_pr_comments_all(self, owner, repo, number): + """ + Get all comments of PR + """ + url_template = "https://gitee.com/api/v5/repos/{owner}/{repo}/pulls/{number}/comments" + url = url_template.format(owner=owner, repo=repo, number=number) + return self.__get_gitee_json(url) + + def edit_pr_comment(self, owner, repo, comment_id, body): + """ + edit a comment + """ + url_template = "https://gitee.com/api/v5/repos/{owner}/{repo}/pulls/comments/{comment_id}" + url = url_template.format(owner=owner, repo=repo, comment_id=comment_id) + values = {} + values["access_token"] = self.token["access_token"] + values["body"] = body + return self.__patch_gitee(url, values) - def get_gitee(self, url, headers=None): + def __get_gitee(self, url, headers=None): """ GET from gitee api """ @@ -145,16 +180,16 @@ class Gitee(): """ url_template = "https://gitee.com/api/v5/repos/{owner}/{repo}/pulls/{number}" url = url_template.format(owner=owner, repo=repo, number=num) - return self.get_gitee_json(url) + return self.__get_gitee_json(url) - def get_gitee_json(self, url): + def __get_gitee_json(self, url): """ Get and load gitee json response """ json_resp = [] headers = self.headers.copy() headers["Content-Type"] = "application/json;charset=UTF-8" - resp = self.get_gitee(url, headers) + resp = self.__get_gitee(url, headers) if resp: json_resp = json.loads(resp) return json_resp @@ -164,7 +199,7 @@ class Gitee(): Get upgrade branch info """ upgrade_branches_url = self.advisor_url + "advisors/helper/upgrade_branches.yaml" - resp = self.get_gitee(upgrade_branches_url) + resp = self.__get_gitee(upgrade_branches_url) if not resp: print("ERROR: upgrade_branches.yaml may not exist.") sys.exit(1) @@ -180,7 +215,7 @@ class Gitee(): Get well known spec file exception """ specfile_exception_url = self.advisor_url + "advisors/helper/specfile_exceptions.yaml" - resp = self.get_gitee(specfile_exception_url) + resp = self.__get_gitee(specfile_exception_url) if not resp: print("ERROR: specfile_exceptions.yaml may not exist.") sys.exit(1) @@ -194,7 +229,7 @@ class Gitee(): Get version recommend exceptions """ version_exception_url = self.advisor_url + "advisors/helper/version_exceptions.yaml" - resp = self.get_gitee(version_exception_url) + resp = self.__get_gitee(version_exception_url) if not resp: print("ERROR: version_exceptions.yaml may not exist.") sys.exit(1) @@ -210,7 +245,7 @@ class Gitee(): excpt = self.get_spec_exception(pkg) if excpt: specurl = urllib.parse.urljoin(specurl, os.path.join(excpt["dir"], excpt["file"])) - resp = self.get_gitee(specurl) + resp = self.__get_gitee(specurl) return resp def get_yaml(self, pkg): @@ -218,11 +253,11 @@ class Gitee(): Get upstream yaml metadata for specific package """ yamlurl = self.advisor_url + "upstream-info/{}.yaml".format(pkg) - resp = self.get_gitee(yamlurl) + resp = self.__get_gitee(yamlurl) if not resp: yamlurl = self.src_openeuler_url + "{repo}.yaml" yamlurl = yamlurl.format(repo=pkg, br="master") - resp = self.get_gitee(yamlurl) + resp = self.__get_gitee(yamlurl) if not resp: print("WARNING: {}.yaml can't be found in upstream-info and repo.".format(pkg)) return resp @@ -233,7 +268,7 @@ class Gitee(): """ yamlurl = "https://gitee.com/api/v5/repos/openeuler/community/contents/"\ "repository/{repo}.yaml".format(repo=repo) - resp = self.get_gitee_json(yamlurl) + resp = self.__get_gitee_json(yamlurl) resp_str = base64.b64decode(resp["content"]) return resp_str @@ -243,7 +278,7 @@ class Gitee(): """ issues_url = "https://gitee.com/api/v5/repos/{prj}/{pkg}/issues?".format(prj=prj, pkg=pkg) parameters = "state=open&sort=created&direction=desc&page=1&per_page=20" - return self.get_gitee_json(issues_url + parameters) + return self.__get_gitee_json(issues_url + parameters) def get_issue_comments(self, pkg, prj="src-openeuler"): """ @@ -251,7 +286,7 @@ class Gitee(): """ issues_url = "https://gitee.com/api/v5/repos/{prj}/{pkg}/issues?".format(prj=prj, pkg=pkg) parameters = "number={num}&page=1&per_page=20&order=asc" - return self.get_gitee_json(issues_url + parameters) + return self.__get_gitee_json(issues_url + parameters) def post_issue(self, pkg, title, body, prj="src-openeuler"): """ @@ -263,7 +298,7 @@ class Gitee(): parameters["repo"] = pkg parameters["title"] = title parameters["body"] = body - self.post_gitee(issues_url, parameters) + self.__post_gitee(issues_url, parameters) def post_issue_comment(self, pkg, number, comment, prj="src-openeuler"): """ @@ -274,7 +309,7 @@ class Gitee(): parameters = {} parameters["access_token"] = self.token["access_token"] parameters["body"] = comment - self.post_gitee(issues_url, parameters) + self.__post_gitee(issues_url, parameters) def get_gitee_datetime(self, time_string): """ diff --git a/advisors/review_tool.py b/advisors/review_tool.py index 8cf335d8..cea2a251 100755 --- a/advisors/review_tool.py +++ b/advisors/review_tool.py @@ -28,13 +28,12 @@ CHK_TABLE_HEADER = """ **以下为 openEuler-Advisor 的 review_tool 生成审视要求清单** 如果您是第一次给 openEuler 提交 PR,建议您花一点时间阅读 [Gitee工作流说明](https://gitee.com/openeuler/community/blob/master/zh/contributors/Gitee-workflow.md) -**[Y]** 审视者确认符合要求 | **[N]** 审视者认为不符合要求 | **[NA]** 审视者认为与本PR无关 | **[?]** 审视者无法确认是否符合要求 | **[ ]** 审视过程中 +**{go}** 审视者确认符合要求 | **{nogo}** 审视者认为不符合要求 | **{na}** 审视者认为与本PR无关 | **{question}** 审视者无法确认是否符合要求 | **{ongoing}** 审视过程中 +**NOTE:** use command: "/review status[go/nogo/na/question/ongoing] number_list[0 1 2 ...]" to update status. |审视项编号|审视类别|审视要求|审视要求说明|审视结果| |:--:|:--:|:--|:--|:--:| """ -SANITY_CHK_CMD = "python3 zh/technical-committee/governance/sanity_check.py ." - CHECKLIST = "helper/reviewer_checklist.yaml" categorizer = {'PRSubmissionSPEC':'PR提交规范', @@ -47,6 +46,16 @@ SIGS_URL = "https://gitee.com/openeuler/community/raw/master/sig/sigs.yaml" headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW 64; rv:23.0) Gecko/20100101 Firefox/23.0'} __NUMBER = 0 +RRVIEW_STATUS = { + 'go':'[🟢]', + 'nogo':'[🔴]', + 'na':'[◯]', + 'question':'[🟡]', + 'ongoing':'[🔵]' + } + +FLAG_EDIT_ALL = 999 + def check_new_code(branch): """ @@ -126,7 +135,8 @@ def join_check_item(category, claim, explain): join check item as a table row """ global __NUMBER - res = "|" + str(__NUMBER) + "|" + category + "|" + claim + "|" + explain + "|[ ]|\n" + item_template = "|{}|{}|{}|{}|{}|\n" + res = item_template.format(__NUMBER, category, claim, explain, RRVIEW_STATUS['ongoing']) __NUMBER += 1 return res @@ -252,8 +262,8 @@ def check_repository_ownership_changes(info): for sig_changes, repos in repo_changes.items(): sig1_owners = load_sig_owners(sig_changes[0]) sig2_owners = load_sig_owners(sig_changes[1]) + repos_need_lgtm = [] if sig_changes[1] == 'sig-recycle': - repos_need_lgtm = [] for repo in repos: if repo.startswith('openeuler/'): mgmt_repos = oe_mgmt_repos @@ -479,7 +489,11 @@ def review(pull_request, repo_name, chklist_path, branch): if not pull_request["mergeable"]: return "PR中存在冲突,无法自动合并。需要先解决冲突,才可以开展评审。" - review_body = CHK_TABLE_HEADER + review_body = CHK_TABLE_HEADER.format(go=RRVIEW_STATUS['go'], + nogo=RRVIEW_STATUS['nogo'], + na=RRVIEW_STATUS['na'], + question=RRVIEW_STATUS['question'], + ongoing=RRVIEW_STATUS['ongoing']) cklist = load_checklist(chklist_path) review_body += basic_review(cklist, branch) custom_items = cklist['customization'].get(repo_name, None) @@ -539,6 +553,10 @@ def args_parser(cur_path): pars.add_argument("-r", "--reuse", help="Reuse current local git dirctory", action="store_true") pars.add_argument("-w", "--workdir", type=str, help="Work directory.Default is current directory.", default=cur_path) + pars.add_argument("-e", "--edit", nargs="+", type=int, help="Edit review item") + pars.add_argument("-s", "--status", type=str, + choices=['go', 'nogo', 'na', 'question', 'ongoing'], + help="Review item status need to set") return pars.parse_args() @@ -590,6 +608,44 @@ def prepare(args, group, repo_name, pull_id, branch): subprocess.call(["git", "merge", "--no-edit", "remotes/origin/" + branch]) +def find_review_comment(user_gitee, group, repo_name, pull_id): + """ + Find the review comment for PR + """ + review_key = "以下为 openEuler-Advisor 的 review_tool 生成审视要求清单" + data = user_gitee.get_pr_comments_all(group, repo_name, pull_id) + for comment in data: + if review_key in comment['body']: + return comment + return None + +def edit_review_status(args, user_gitee, group ,repo_name, pull_id): + """ + Edit review status + """ + comment = find_review_comment(user_gitee, group, repo_name, pull_id) + if not comment: + print("ERROR: can not find review list") + sys.exit(1) + items = comment['body'].splitlines(True) + need_edit = False + head_len = len(CHK_TABLE_HEADER.splitlines()) + if len(args.edit) == 1 and args.edit[0] == FLAG_EDIT_ALL: + need_edit = True + for num in range(len(items[head_len:])): + items[head_len+num] = re.sub(r"\[.*\]", RRVIEW_STATUS[args.status], items[head_len+num]) + else: + for num in args.edit: + if int(num) >=0 and int(num) < len(items[head_len:]): + items[head_len+num] = re.sub(r"\[.*\]", + RRVIEW_STATUS[args.status], + items[head_len+num]) + need_edit = True + if need_edit: + new_body = "".join(items) + user_gitee.edit_pr_comment(group, repo_name, comment['id'], new_body) + + def main(): """ Main entrance of the functionality @@ -609,14 +665,17 @@ def main(): print("Failed to get PR:%s of repository:%s, make sure the PR is exist."\ % (pull_id, repo_name)) sys.exit(1) - branch = pull_request['base']['label'] + if args.edit and args.status: + edit_review_status(args, user_gitee, group, repo_name, pull_id) + else: + branch = pull_request['base']['label'] - prepare(args, group, repo_name, pull_id, branch) + prepare(args, group, repo_name, pull_id, branch) - chklist_path = os.path.join(cur_path, CHECKLIST) - review_comment = review(pull_request, repo_name, chklist_path, branch) + chklist_path = os.path.join(cur_path, CHECKLIST) + review_comment = review(pull_request, repo_name, chklist_path, branch) - user_gitee.create_pr_comment(repo_name, pull_id, review_comment, group) + user_gitee.create_pr_comment(repo_name, pull_id, review_comment, group) if __name__ == "__main__": -- Gitee