diff --git a/github-bot/harvester_github_bot/__init__.py b/github-bot/harvester_github_bot/__init__.py index 46517b0..03e6dc2 100644 --- a/github-bot/harvester_github_bot/__init__.py +++ b/github-bot/harvester_github_bot/__init__.py @@ -1,2 +1,3 @@ +from harvester_github_bot.global_variables import * from harvester_github_bot.config import * from harvester_github_bot.route import * diff --git a/github-bot/harvester_github_bot/action_added_to_default_project.py b/github-bot/harvester_github_bot/action_added_to_default_project.py new file mode 100644 index 0000000..7f7934e --- /dev/null +++ b/github-bot/harvester_github_bot/action_added_to_default_project.py @@ -0,0 +1,23 @@ +from harvester_github_bot.issue_transfer import IssueTransfer +from harvester_github_bot.action import Action +from harvester_github_bot import app, gtihub_project_manager, \ + ZENHUB_PIPELINE, ZENHUB_PIPELINE + +class ActionAddedToDefaultProject(Action): + def __init__(self): + pass + + def isMatched(self, actionRequest): + if actionRequest.event_type not in ['issue']: + return False + if actionRequest.action not in ['opened']: + return False + return True + + def action(self, request): + if gtihub_project_manager.prepared is False: + return + + issue = request.get('issue') + item = gtihub_project_manager.get_issue(issue["number"]) + gtihub_project_manager.add_issue_to_project(item['id']) \ No newline at end of file diff --git a/github-bot/harvester_github_bot/action_project.py b/github-bot/harvester_github_bot/action_project.py index 7bc3f0f..17d9b10 100644 --- a/github-bot/harvester_github_bot/action_project.py +++ b/github-bot/harvester_github_bot/action_project.py @@ -22,6 +22,12 @@ def action(self, request): if gtihub_project_manager.project()["id"] != project_node_id: app.logger.error("project is not matched") return + + # In github projectv2, every status filed changed will trigger a projectv2 event + # For example, changing a `Estimate` and `Status`. + # But, we only care about the `Status` field. + if request['changes']['field_value']['field_name'] != "Status": + return target_column = request['changes']['field_value']['to'] if target_column["name"] not in ZENHUB_PIPELINE.split(","): diff --git a/github-bot/harvester_github_bot/action_sync_milestone.py b/github-bot/harvester_github_bot/action_sync_milestone.py index 4e8deaa..00021a2 100644 --- a/github-bot/harvester_github_bot/action_sync_milestone.py +++ b/github-bot/harvester_github_bot/action_sync_milestone.py @@ -9,7 +9,7 @@ def __init__(self): def isMatched(self, actionRequest): if actionRequest.event_type not in ['issue']: return False - if actionRequest.action not in ['opened', 'milestoned', 'demilestoned']: + if actionRequest.action not in ['milestoned', 'demilestoned']: return False return True def action(self, request): diff --git a/github-bot/harvester_github_bot/config.py b/github-bot/harvester_github_bot/config.py index 3a023a0..2220263 100644 --- a/github-bot/harvester_github_bot/config.py +++ b/github-bot/harvester_github_bot/config.py @@ -7,6 +7,7 @@ from github import Github from harvester_github_bot.github_graphql.manager import GitHubProjectManager from harvester_github_bot.zenhub import Zenhub +from harvester_github_bot.global_variables import * FLASK_LOGLEVEL = "" FLASK_PASSWORD = "" @@ -18,12 +19,12 @@ ZENHUB_PIPELINE = "" BACKPORT_LABEL_KEY = "" -app = Flask(__name__) -gh_api = {} -zenh_api = {} -repo = {} -repo_test = {} -gtihub_project_manager = {} +# app = Flask(__name__) +# gh_api = {} +# zenh_api = {} +# repo = {} +# repo_test = {} +# gtihub_project_manager = {} class BotConfig(RequiredConfigMixin): required_config = ConfigOptions() diff --git a/github-bot/harvester_github_bot/github_graphql/manager.py b/github-bot/harvester_github_bot/github_graphql/manager.py index 52377b3..ec2db8e 100644 --- a/github-bot/harvester_github_bot/github_graphql/manager.py +++ b/github-bot/harvester_github_bot/github_graphql/manager.py @@ -1,6 +1,7 @@ import requests +from harvester_github_bot import app from harvester_github_bot.github_graphql.ql_queries import GET_ISSUE_QUERY, GET_GLOBAL_ISSUE_QUERY, GET_ORGANIZATION_PROJECT_QUERY -from harvester_github_bot.github_graphql.ql_mutation import ADD_ISSUE_TO_PROJECT_MUTATION +from harvester_github_bot.github_graphql.ql_mutation import ADD_ISSUE_TO_PROJECT_MUTATION, MOVE_ISSUE_TO_STATUS class GitHubProjectManager: def __init__(self, organization, repository, project_number, headers): @@ -11,12 +12,20 @@ def __init__(self, organization, repository, project_number, headers): try: self.__project = self.__get_orgnization_project(project_number) + self.status_node_id, self.status = self.get_status_fields() # This is a temporary solution to make sure Github Project things don't break Zenhub when Github Project is not found. # After deprecate the Zenhub, we will remove this `prepared` part. self.prepared = True - except: + except Exception as e: + app.logger.exception(f"Failed to get project information : {str(e)}") self.prepared = False + + def get_status_fields(self): + nodes = self.__project.get("fields").get("nodes") + for node in nodes: + if node.get("name") == "Status": + return node.get("id"), {option.get("name"): option.get("id") for option in node.get("options")} def project(self): return self.__project @@ -53,6 +62,20 @@ def add_issue_to_project(self, issue_id): return response.json() else: raise Exception(f"Mutation failed to run by returning code of {response.status_code}. {response.json()}") + + def move_issue_to_status(self, issue_id, status_name): + variables = { + 'project_id': self.__project["id"], + 'item_id': issue_id, + 'field_id': self.status_node_id, + 'single_select_option_id': self.status[status_name] + } + + response = requests.post(self.url, headers=self.headers, json={'query': MOVE_ISSUE_TO_STATUS, '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 = { diff --git a/github-bot/harvester_github_bot/github_graphql/ql_mutation.py b/github-bot/harvester_github_bot/github_graphql/ql_mutation.py index 6e2d348..87ff26d 100644 --- a/github-bot/harvester_github_bot/github_graphql/ql_mutation.py +++ b/github-bot/harvester_github_bot/github_graphql/ql_mutation.py @@ -6,4 +6,14 @@ } } } +""" + +MOVE_ISSUE_TO_STATUS = """ + mutation($project_id: ID!, $item_id: ID!, $field_id: ID!, $single_select_option_id: String!) { + updateProjectV2ItemFieldValue(input: {projectId: $project_id, itemId: $item_id, fieldId: $field_id, value: {singleSelectOptionId: $single_select_option_id}}) { + projectV2Item { + 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 index 272c941..1a7d9b4 100644 --- a/github-bot/harvester_github_bot/github_graphql/ql_queries.py +++ b/github-bot/harvester_github_bot/github_graphql/ql_queries.py @@ -19,6 +19,22 @@ id title number + fields(first: 20) { + nodes { + ... on ProjectV2FieldCommon { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } + } + } } } } diff --git a/github-bot/harvester_github_bot/global_variables.py b/github-bot/harvester_github_bot/global_variables.py new file mode 100644 index 0000000..621f169 --- /dev/null +++ b/github-bot/harvester_github_bot/global_variables.py @@ -0,0 +1,9 @@ + +from flask import Flask + +app = Flask(__name__) +gh_api = {} +zenh_api = {} +repo = {} +repo_test = {} +gtihub_project_manager = {} \ 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 c297196..f6d7816 100644 --- a/github-bot/harvester_github_bot/route.py +++ b/github-bot/harvester_github_bot/route.py @@ -10,6 +10,7 @@ from harvester_github_bot.action_label import ActionLabel from harvester_github_bot.action_sync_milestone import ActionSyncMilestone from harvester_github_bot.action_project import ActionProject +from harvester_github_bot.action_added_to_default_project import ActionAddedToDefaultProject auth = HTTPBasicAuth() @@ -41,6 +42,7 @@ def zenhub(): ActionLabel(), ActionSyncMilestone(), ActionProject(), + ActionAddedToDefaultProject(), ] SUPPORTED_EVENT = [