From f0db0281a284a6c6d84626ecf4431dcb30f19e43 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 6 Aug 2024 14:35:03 +0800 Subject: [PATCH] feat: create backport issue in github project instead of zenhub Signed-off-by: Jack Yu --- .../action_sync_milestone.py | 39 ------------- github-bot/harvester_github_bot/config.py | 18 ++++-- .../github_graphql/manager.py | 58 +++++++++++++++++++ .../github_graphql/ql_mutation.py | 9 +++ .../github_graphql/ql_queries.py | 38 ++++++++++++ .../label_action/create_backport.py | 17 +----- .../label_action/create_gui_issue.py | 9 ++- github-bot/harvester_github_bot/route.py | 2 - 8 files changed, 127 insertions(+), 63 deletions(-) delete mode 100644 github-bot/harvester_github_bot/action_sync_milestone.py create mode 100644 github-bot/harvester_github_bot/github_graphql/manager.py create mode 100644 github-bot/harvester_github_bot/github_graphql/ql_mutation.py create mode 100644 github-bot/harvester_github_bot/github_graphql/ql_queries.py diff --git a/github-bot/harvester_github_bot/action_sync_milestone.py b/github-bot/harvester_github_bot/action_sync_milestone.py deleted file mode 100644 index dfac1cd..0000000 --- a/github-bot/harvester_github_bot/action_sync_milestone.py +++ /dev/null @@ -1,39 +0,0 @@ -from harvester_github_bot import app -from harvester_github_bot import zenh_api, repo -from harvester_github_bot.exception import CustomException -from harvester_github_bot.action import Action - -class ActionSyncMilestone(Action): - def __init__(self): - pass - def isMatched(self, actionRequest): - if actionRequest.action not in ['opened', 'milestoned', 'demilestoned']: - return False - return True - def action(self, request): - return sync_milestone(request) - -def sync_milestone(request): - issue = request.get('issue') - milestone = issue['milestone'] - milestone_title = milestone['title'] if milestone is not None else "" - - if milestone_title != "": - app.logger.debug('set releases: repo.id=%d, milestone=%s, number=%s', - repo.id, - milestone_title, - issue["number"]) - else: - app.logger.debug('clean releases: repo.id=%d, number=%s', repo.id, issue["number"]) - - try: - zenh_api.clean_releases_to_issue_except_by_milestone( - repo.id, milestone_title, issue["number"] - ) - return "sync milestone success" - except CustomException as e: - app.logger.exception(f"Custom exception : {str(e)}") - except Exception as e: - app.logger.exception(e) - - return "sync milestone failed" \ No newline at end of file diff --git a/github-bot/harvester_github_bot/config.py b/github-bot/harvester_github_bot/config.py index 84f9dfd..3a023a0 100644 --- a/github-bot/harvester_github_bot/config.py +++ b/github-bot/harvester_github_bot/config.py @@ -5,7 +5,7 @@ from everett.manager import ConfigManager, ConfigOSEnv from werkzeug.security import generate_password_hash from github import Github - +from harvester_github_bot.github_graphql.manager import GitHubProjectManager from harvester_github_bot.zenhub import Zenhub FLASK_LOGLEVEL = "" @@ -14,6 +14,7 @@ GITHUB_OWNER = "" GITHUB_REPOSITORY = "" GITHUB_REPOSITORY_TEST = "" +GITHUB_PROJECT_NUMBER = "" ZENHUB_PIPELINE = "" BACKPORT_LABEL_KEY = "" @@ -22,7 +23,7 @@ zenh_api = {} repo = {} repo_test = {} - +gtihub_project_manager = {} class BotConfig(RequiredConfigMixin): required_config = ConfigOptions() @@ -36,6 +37,8 @@ class BotConfig(RequiredConfigMixin): 'GitHub repository.') required_config.add_option('github_repository_test', parser=str, default='tests', doc='Set the name of the tests ' 'GitHub repository.') + required_config.add_option('github_project_number', parser=int, doc='Set the project id of the github ' + 'GitHub Project ID.') required_config.add_option('github_token', parser=str, doc='Set the token of the GitHub machine user.') required_config.add_option('zenhub_pipeline', parser=str, default='Review,Ready For Testing,Testing', doc='Set the target ZenHub pipeline to ' @@ -53,8 +56,8 @@ def get_config(): def settings(): - global FLASK_LOGLEVEL, FLASK_PASSWORD, FLASK_USERNAME, GITHUB_OWNER, GITHUB_REPOSITORY, GITHUB_REPOSITORY_TEST, \ - ZENHUB_PIPELINE, BACKPORT_LABEL_KEY, gh_api, zenh_api, repo, repo_test + global FLASK_LOGLEVEL, FLASK_PASSWORD, FLASK_USERNAME, GITHUB_OWNER, GITHUB_REPOSITORY, GITHUB_PROJECT_NUMBER, GITHUB_REPOSITORY_TEST, \ + ZENHUB_PIPELINE, BACKPORT_LABEL_KEY, gh_api, zenh_api, repo, repo_test, gtihub_project_manager config = get_config() FLASK_LOGLEVEL = config('flask_loglevel') FLASK_PASSWORD = generate_password_hash(config('flask_password')) @@ -62,19 +65,22 @@ def settings(): GITHUB_OWNER = config('github_owner') GITHUB_REPOSITORY = config('github_repository') GITHUB_REPOSITORY_TEST = config('github_repository_test') + GITHUB_PROJECT_NUMBER = config('github_project_number') ZENHUB_PIPELINE = config('zenhub_pipeline') BACKPORT_LABEL_KEY = config('backport_label_key', default='backport-needed') gh_api = Github(config('github_token')) zenh_api = Zenhub(config('zenhub_token')) - repo = gh_api.get_repo('{}/{}'.format(GITHUB_OWNER, GITHUB_REPOSITORY)) repo_test = gh_api.get_repo('{}/{}'.format(GITHUB_OWNER, GITHUB_REPOSITORY_TEST)) + gtihub_project_manager = GitHubProjectManager(GITHUB_OWNER, GITHUB_REPOSITORY, GITHUB_PROJECT_NUMBER, { + 'Authorization': f'Bearer {config("github_token")}', + 'Content-Type': 'application/json' + }) numeric_level = getattr(logging, FLASK_LOGLEVEL.upper(), None) if not isinstance(numeric_level, int): raise ValueError('invalid log level: %s'.format(FLASK_LOGLEVEL)) app.logger.setLevel(level=numeric_level) - settings() diff --git a/github-bot/harvester_github_bot/github_graphql/manager.py b/github-bot/harvester_github_bot/github_graphql/manager.py new file mode 100644 index 0000000..76570c6 --- /dev/null +++ b/github-bot/harvester_github_bot/github_graphql/manager.py @@ -0,0 +1,58 @@ +import requests +from harvester_github_bot.github_graphql.ql_queries import GET_ISSUE_QUERY, GET_ORGANIZATION_PROJECT_QUERY, GET_USER_PROJECT_QUERY +from harvester_github_bot.github_graphql.ql_mutation import ADD_ISSUE_TO_PROJECT_MUTATION + +class GitHubProjectManager: + def __init__(self, organization, repository, project_number, headers): + self.organization = organization + self.repository = repository + self.headers = headers + self.url = "https://api.github.com/graphql" + self.__project = self.__get_orgnization_project(project_number) + # self.__project = self.__get_user_project(project_number) # used in testing + + def get_issue(self, issue_number): + variables = { + 'repo_owner': self.organization, + 'repo_name': self.repository, + 'issue_number': issue_number + } + response = requests.post(self.url, headers=self.headers, json={'query': GET_ISSUE_QUERY, 'variables': variables}) + if response.status_code == 200: + return response.json()['data']['repository']['issue'] + else: + raise Exception(f"Query failed to run by returning code of {response.status_code}. {response.json()}") + + def add_issue_to_project(self, issue_id): + variables = { + 'project_id': self.__project["id"], + 'content_id': issue_id + } + response = requests.post(self.url, headers=self.headers, json={'query': ADD_ISSUE_TO_PROJECT_MUTATION, 'variables': variables}) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"Mutation failed to run by returning code of {response.status_code}. {response.json()}") + + def __get_orgnization_project(self, project_number): + variables = { + 'organization': self.organization, + 'project_number': project_number + } + response = requests.post(self.url, headers=self.headers, json={'query': GET_ORGANIZATION_PROJECT_QUERY, 'variables': variables}) + if response.status_code == 200: + return response.json()['data']['organization']['projectV2'] + else: + raise Exception(f"Query failed to run by returning code of {response.status_code}. {response.json()}") + + + def __get_user_project(self, project_number): + variables = { + 'organization': self.organization, + 'project_number': project_number + } + response = requests.post(self.url, headers=self.headers, json={'query': GET_USER_PROJECT_QUERY, 'variables': variables}) + if response.status_code == 200: + return response.json()['data']['user']['projectV2'] + else: + raise Exception(f"Query failed to run by returning code of {response.status_code}. {response.json()}") \ No newline at end of file diff --git a/github-bot/harvester_github_bot/github_graphql/ql_mutation.py b/github-bot/harvester_github_bot/github_graphql/ql_mutation.py new file mode 100644 index 0000000..6e2d348 --- /dev/null +++ b/github-bot/harvester_github_bot/github_graphql/ql_mutation.py @@ -0,0 +1,9 @@ +ADD_ISSUE_TO_PROJECT_MUTATION = """ +mutation($project_id: ID!, $content_id: ID!) { + addProjectV2ItemById(input: {projectId: $project_id, contentId: $content_id}) { + item { + id + } + } +} +""" \ No newline at end of file diff --git a/github-bot/harvester_github_bot/github_graphql/ql_queries.py b/github-bot/harvester_github_bot/github_graphql/ql_queries.py new file mode 100644 index 0000000..0816b46 --- /dev/null +++ b/github-bot/harvester_github_bot/github_graphql/ql_queries.py @@ -0,0 +1,38 @@ +GET_ISSUE_QUERY = """ +query($repo_owner: String!, $repo_name: String!, $issue_number: Int!) { + repository(owner: $repo_owner, name: $repo_name) { + issue(number: $issue_number) { + id + number + title + url + } + } +} +""" + + +GET_ORGANIZATION_PROJECT_QUERY = """ +query($organization: String!, $project_number: Int!) { + organization(login: $organization) { + projectV2(number: $project_number) { + id + title + number + } + } +} +""" + +# This is used to test +GET_USER_PROJECT_QUERY = """ +query($organization: String!, $project_number: Int!) { + user(login: $organization) { + projectV2(number: $project_number) { + id + title + number + } + } +} +""" \ No newline at end of file diff --git a/github-bot/harvester_github_bot/label_action/create_backport.py b/github-bot/harvester_github_bot/label_action/create_backport.py index 3d8dfba..02c08aa 100644 --- a/github-bot/harvester_github_bot/label_action/create_backport.py +++ b/github-bot/harvester_github_bot/label_action/create_backport.py @@ -1,5 +1,5 @@ import re -from harvester_github_bot import app, zenh_api, repo, \ +from harvester_github_bot import app, gtihub_project_manager, repo, \ BACKPORT_LABEL_KEY from harvester_github_bot.exception import CustomException, ExistedBackportComment from harvester_github_bot.label_action.create_gui_issue import CREATE_GUI_ISSUE_LABEL @@ -132,17 +132,6 @@ def create_comment(self): # associated zenhub releases def related_release(self): - release_id = zenh_api.get_release_id_by_version(repo_id=repo.id, version=self.__ver) - - if release_id is not None and len(release_id) > 0: - try: - zenh_api.add_release_to_issue( - repo_id=repo.id, - release_id=release_id, - issue_number=self.__issue.number - ) - except Exception as e: - raise CustomException("failed to associated release(%s) with repo(%d) and issue(%d): %s" % ( - release_id, repo.id, self.__issue.number, e)) - + issue = gtihub_project_manager.get_issue(self.__issue.number) + gtihub_project_manager.add_issue_to_project(issue['id']) return "issue number: %d." % self.__issue.number diff --git a/github-bot/harvester_github_bot/label_action/create_gui_issue.py b/github-bot/harvester_github_bot/label_action/create_gui_issue.py index c3787ac..562802d 100644 --- a/github-bot/harvester_github_bot/label_action/create_gui_issue.py +++ b/github-bot/harvester_github_bot/label_action/create_gui_issue.py @@ -1,4 +1,4 @@ -from harvester_github_bot import repo +from harvester_github_bot import repo, gtihub_project_manager from harvester_github_bot.action import LabelAction import re @@ -41,6 +41,7 @@ def action(self, request): return "GUI issue already exist" self.__create_gui_issue() self.__create_comment() + self.__added_into_project() return "create GUI issue success" def __create_gui_issue(self): @@ -62,4 +63,8 @@ def __is_gui_issue_exist(self): for comment in comments: if re.match(comment_pattern, comment.body): return True - return False \ No newline at end of file + return False + + def __added_into_project(self): + issue = gtihub_project_manager.get_issue(self.gui_issue.number) + gtihub_project_manager.add_issue_to_project(issue['id']) \ No newline at end of file diff --git a/github-bot/harvester_github_bot/route.py b/github-bot/harvester_github_bot/route.py index cc4d2ff..dc02878 100644 --- a/github-bot/harvester_github_bot/route.py +++ b/github-bot/harvester_github_bot/route.py @@ -8,7 +8,6 @@ from harvester_github_bot.issue_transfer import issue_transfer from harvester_github_bot.action import ActionRequest from harvester_github_bot.action_label import ActionLabel -from harvester_github_bot.action_sync_milestone import ActionSyncMilestone auth = HTTPBasicAuth() @@ -41,7 +40,6 @@ def zenhub(): SUPPORTED_ACTIONS = [ ActionLabel(), - ActionSyncMilestone(), ] @app.route('/github', methods=['POST'])