diff options
| author | Pheenoh <pheenoh@gmail.com> | 2023-08-09 16:27:37 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-09 16:27:37 -0600 |
| commit | 244892aeb849a9ad9801aa9828e6282d29df87f0 (patch) | |
| tree | d0068b429e5b5ac45d2b62c8bc48509df41da204 /tools/libgithub/issue.py | |
| parent | bb546a286f8c6f18c5fb1da152a34a82d17dbbaa (diff) | |
Github Projects (#1872)
* github projects
* add project prefixes
Diffstat (limited to 'tools/libgithub/issue.py')
| -rw-r--r-- | tools/libgithub/issue.py | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/tools/libgithub/issue.py b/tools/libgithub/issue.py new file mode 100644 index 0000000000..37bd3fee18 --- /dev/null +++ b/tools/libgithub/issue.py @@ -0,0 +1,443 @@ +import sys + +from .label import * +from typing import Optional + +@dataclass +class Issue: + yaml_data: Optional[dict] = None + + def __eq__(self, other): + if isinstance(other, Issue): + return self.unique_id == other.unique_id + return False + + def __hash__(self): + return hash(self.unique_id) + + def __init__(self,id=None, title=None, body=None, label_ids=None, file_path=None, data=None): + if data is not None: + self.yaml_ctor(data) + else: + self.id = id + self.title = title + self.body = body + self.label_ids = label_ids + self.file_path = file_path + + def yaml_ctor(self,data): + for tu, labels, file_path in get_translation_units(data): + self.id = None + self.title = tu + self.body = None + self.file_path = file_path + self.label_ids = [] + + def set_open(self): + pass + + def set_closed(self): + LOG.debug(f'Closing issue {self.id}') + + mutation = """ + mutation CloseIssue($id: ID!) { + closeIssue(input: {issueId: $id}) { + clientMutationId + } + } + """ + + variables = { + "id": self.id + } + + data = GraphQLClient.get_instance().make_request(mutation, variables) + if data: + LOG.info(f'Closed issue {self.id}') + else: + LOG.error(f'Failed to close issue {self.id}') + sys.exit(1) + + + + + @staticmethod + def delete_all(): + LOG.debug(f'Deleting all issues in {RepoInfo.owner.name}/{RepoInfo.name}') + + issue_state = StateFile.data["issues"] + if issue_state is not None and len(issue_state) > 0: + for issue in issue_state.copy(): + Issue( + file_path=issue["file_path"], + id=issue["id"], + title=issue["file_path"] + ).delete() + else: + LOG.warning(f'No issues found in state file, nothing to delete.') + return + + @staticmethod + def get_all_from_github() -> list['Issue']: + return Issue.get_open() + Issue.get_closed() + + @staticmethod + def get_closed() -> list['Issue']: + return Issue.get_by_state("CLOSED") + + @staticmethod + def get_open() -> list['Issue']: + return Issue.get_by_state("OPEN") + + @staticmethod + def get_all_from_yaml(data): + ret_issues = [] + labels_dict = {label['name']: label['id'] for label in StateFile.data["labels"]} + + for d in data: + # Get tu, labels, filepath for current project + tu_info = get_translation_units(d) + + # Add in project name as a label + for idx, (tu, labels, path) in enumerate(tu_info): + new_labels = labels + [d['project']['title']] + tu_info[idx] = (tu, new_labels, path) + + + for tu, labels, file_path in tu_info: + state_label_ids = [] + + # Fetch label ids from state file + for label in labels: + if label in labels_dict: + LOG.debug(f'Found label {label} for TU {tu} in state file, adding to issue.') + state_label_ids.append(labels_dict[label]) + else: + LOG.error(f'Label {label} not found in state file, please run ./tp github-sync-labels first.') + sys.exit(1) + + issue = Issue( + id=None, # set in check_and_create or create method + title=tu, + body=None, + label_ids=state_label_ids, + file_path=file_path + ) + + ret_issues.append(issue) + + return ret_issues + + @staticmethod + def get_by_unique_id(unique_id: str) -> 'Issue': + LOG.debug(f'Getting issue with unique ID {unique_id} on {RepoInfo.owner.name}/{RepoInfo.name}') + + query = ''' + query ($owner: String!, $repo: String!, $first: Int!, $after: String) { + repository(owner: $owner, name: $repo) { + issues(first: $first, after: $after) { + pageInfo { + endCursor + hasNextPage + } + nodes { + title + id + body + labels(first: 100) { + nodes { + id + name + } + } + } + } + } + } + ''' + + has_next_page = True + cursor = None + + while has_next_page: + variables = { + "owner": RepoInfo.owner.name, + "repo": RepoInfo.name, + "first": 100, + "after": cursor + } + + data = GraphQLClient.get_instance().make_request(query, variables) + if data: + issues = data['data']['repository']['issues']['nodes'] + for issue in issues: + labels = sorted([label['name'] for label in issue['labels']['nodes']]) + issue_unique_id = issue['title'] + '-' + '-'.join(labels) + if issue_unique_id == unique_id: + return Issue( + id=issue['id'], + title=issue['title'], + body=issue['body'], + labels=[ + Label( + id=label['id'], + name=label['name'] + ) for label in issue['labels']['nodes'] + ], + unique_id=unique_id + ) + + page_info = data['data']['repository']['issues']['pageInfo'] + has_next_page = page_info['hasNextPage'] + cursor = page_info['endCursor'] + else: + LOG.warning(f'No issue found with unique ID {unique_id}') + return None + + @staticmethod + def get_by_state(state: str) -> list['Issue']: + LOG.debug(f'Getting {state} issues on {RepoInfo.owner.name}/{RepoInfo.name}') + + query = ''' + query ($owner: String!, $repo: String!, $state: [IssueState!], $cursor: String) { + repository(owner: $owner, name: $repo) { + issues(first: 100, states: $state, after: $cursor) { + pageInfo { + endCursor + hasNextPage + } + nodes { + title + id + body + labels(first: 100) { + nodes { + id + name + } + } + } + } + } + } + ''' + + + all_issues = [] + has_next_page = True + cursor = None + + while has_next_page: + variables = { + "owner": RepoInfo.owner.name, + "repo": RepoInfo.name, + "state": state, + "cursor": cursor + } + + data = GraphQLClient.get_instance().make_request(query, variables) + if data: + issues = data['data']['repository']['issues']['nodes'] + for issue in issues: + insert_issue = Issue( + id=issue['id'], + title=issue['title'], + body=issue['body'] + ) + + all_issues.append(insert_issue) + + + LOG.debug(f'{state} issues retrieved: {issues}') + page_info = data['data']['repository']['issues']['pageInfo'] + has_next_page = page_info['hasNextPage'] + cursor = page_info['endCursor'] + + LOG.debug(f'All {state} issues retrieved: {all_issues}') + return all_issues + + @staticmethod + def get_labels_by_id(issue_id: str) -> list[Label]: + LOG.debug(f'Getting all labels for issue {issue_id} on {RepoInfo.owner.name}/{RepoInfo.name}') + + query = ''' + query ($id: ID!) { + node(id: $id) { + ... on Issue { + labels(first: 100) { + nodes { + id + name + } + } + } + } + } + ''' + + variables = { + "id": issue_id + } + + data = GraphQLClient.get_instance().make_request(query, variables) + if data: + labels = data['data']['node']['labels']['nodes'] + LOG.debug(f'Labels retrieved: {labels} for issue {issue_id}') + + ret_labels = [] + for label in labels: + label = Label( + id=label["id"], + name=label["name"] + ) + + ret_labels.append(label) + + return ret_labels + else: + LOG.debug(f'No labels found for issue {issue_id}') + return [] + + def attach_labels(self) -> None: + LOG.debug(f'Attaching labels to issue {self.id} on {RepoInfo.owner.name}/{RepoInfo.name}') + + def check_and_create(self) -> None: + issues = StateFile.data.get('issues') + + if issues is None: + issue_dict = {} + else: + issue_dict = {issue['file_path']: issue for issue in issues} + + if self.file_path in issue_dict: + LOG.info(f"Issue {self.title} from TU {self.file_path} already setup!") + self.id = issue_dict[self.file_path]["id"] + else: + LOG.info(f'Creating missing issue {self.title}.') + self.create() + + # def check_and_attach_labels(self) -> None: + # LOG.debug(f'Checking labels for issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}') + + # issues = StateFile.data.get('issues') + + # if issues is None: + # issue_dict = {} + # else: + # issue_dict = {issue['file_path']: issue for issue in issues} + + # if self.file_path in issue_dict: + # state_labels = StateFile.data.get('labels') + # label_ids = issue_dict[self.file_path]["label_ids"] + + # if label_ids is not None: + # state_label_ids = [label['id'] for label in state_labels] + # for label_id in label_ids: + # if label_id in state_label_ids: + # LOG.debug(f'Label {label_id} exists in state, continuing') + # continue + # else: + # LOG.error(f'Label {label_id} does not exist in state, please run sync-labels first') + # sys.exit(1) + + # LOG.info(f'All labels already attached to issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}') + # else: + # LOG.info(f'Attaching labels to issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}') + + # # use yaml data to fetch label names for this issue + # # lookup id from state and attach to issue + # <replace> = + # for label in <replace>: + # self.attach_label_by_id() # finish + + # LOG.info(f'Labels attached to issue {self.title} on {RepoInfo.owner.name}/{RepoInfo.name}') + + + # print(label_ids) + # sys.exit(0) + # else: + # LOG.error(f"Issue {self.title} from TU {self.file_path} is missing") + # sys.exit(1) + + def create(self): + repo_id = RepoInfo.id + mutation = """ + mutation CreateIssue($input: CreateIssueInput!) { + createIssue(input: $input) { + issue { + id + title + } + } + } + """ + + input_dict = { + "repositoryId": repo_id, + "title": self.title, + "body": self.body, + } + + if self.label_ids is not None: + input_dict["labelIds"] = self.label_ids + + variables = { + "input": input_dict + } + + data = GraphQLClient.get_instance().make_request(mutation, variables) + if data: + self.id = data["data"]["createIssue"]["issue"]["id"] + self.title = data["data"]["createIssue"]["issue"]["title"] + self.write_state_to_file() + LOG.info(f'Created Issue {self.title} with ID {self.id}') + return self.id + + def delete(self) -> None: + LOG.debug(f'Deleting issue {self.title} with ID {self.id}') + mutation = ''' + mutation DeleteIssue($id: ID!) { + deleteIssue(input: {issueId: $id}) { + clientMutationId + } + } + ''' + + variables = { + "id": self.id + } + + data = GraphQLClient.get_instance().make_request(mutation, variables) + if data: + self.write_state_to_file(delete=True) + LOG.info(f'Successfully deleted issue {self.title}.') + else: + LOG.error(f'Failed to delete issue {self.title}') + + def write_state_to_file(self, delete: bool = False): + state = { + "id": self.id, + "title": self.title, + "body": self.body, + "label_ids": self.label_ids, + "file_path": self.file_path, + } + + curr_state_issues = StateFile.data.get("issues", None) + if curr_state_issues is not None: + for i, issue in enumerate(curr_state_issues): + if issue['id'] == self.id: + if delete: + del StateFile.data['issues'][i] + break + else: + StateFile.data['issues'][i] = state + break + else: + StateFile.data['issues'].append((state)) + else: + StateFile.data['issues'] = [state] + + + with open("tools/pjstate.yml", 'w') as f: + yaml.safe_dump(StateFile.data, f)
\ No newline at end of file |
