import dataclasses import logging import os import webbrowser def force_import(module: str): import importlib import subprocess import sys try: return importlib.import_module(module) except ModuleNotFoundError: subprocess.run([sys.executable, "-m", "pip", "install", module]) importlib.invalidate_caches() return importlib.import_module(module) try: import httpx except ImportError: torf = force_import("httpx") try: import typer except ImportError: typer = force_import("typer") try: import rich except ImportError: rich = force_import("rich") try: import inquirer except ImportError: inquirer = force_import("inquirer") from rich import print import rich.table JIRA_EMAIL = os.getenv('JIRA_EMAIL', 'abdussamet.kocak@akinon.com') JIRA_TOKEN = os.getenv('JIRA_TOKEN', 'iAZhlJ1AdGwckbVllhlx48C4') JIRA_URL = os.getenv('JIRA_URL', 'https://omniplatform.atlassian.net/') if not JIRA_TOKEN: logging.info('') webbrowser.open('https://id.atlassian.com/manage-profile/security/api-tokens') @dataclasses.dataclass class Issue: id: str key: str summary: str @dataclasses.dataclass class PullRequest: id: str name: str repository: str branch: str class Jira: def __init__(self, email: str, token: str): self.client = httpx.Client( base_url=JIRA_URL, auth=httpx.BasicAuth(email, token), ) def list_issues_for_release(self) -> list[Issue]: mergeable_status = 'In Review' res = self.client.post( '/rest/api/2/search', json={ 'jql': f'project = COM AND status = "{mergeable_status}" AND "Team[Dropdown]" = Backend ORDER BY created DESC' }, ) res.raise_for_status() data = res.json() development_type = ['Story', 'Bug', 'Task'] return [ Issue(id=it['id'], key=it['key'], summary=it['fields']['summary']) for it in data['issues'] if it['fields']['issuetype']['name'] in development_type ] def get_approved_prs(self, issue_id: str): res = self.client.post( f'/jsw/graphql', params={'operation': 'DevDetailsDialog'}, json={ "operationName": "DevDetailsDialog", "query": """ query DevDetailsDialog ($issueId: ID!) { developmentInformation(issueId: $issueId){ details { instanceTypes { repository { name branches { name pullRequests { name status lastUpdate } reviews { state id } } pullRequests { id name branchName status reviewers{ name isApproved } } } danglingPullRequests { id name branchName status reviewers{ name isApproved } } } } } } """, "variables": {"issueId": issue_id}, }, ) res.raise_for_status() data = res.json()['data'] if not data['developmentInformation']['details']['instanceTypes']: return [] repo_name = data['developmentInformation']['details']['instanceTypes'][0]['repository'][0]['name'] prs = data['developmentInformation']['details']['instanceTypes'][0]['danglingPullRequests'] if not prs: repo_name = data['developmentInformation']['details']['instanceTypes'][0]['repository'][0]['name'] prs = data['developmentInformation']['details']['instanceTypes'][0]['repository'][0]['pullRequests'] return [ PullRequest( id=it['id'], name=it['name'], repository=repo_name, branch=it['branchName'], ) for it in prs if it['status'] != 'MERGED' ] console = rich.console.Console() def render_issue(issue: Issue, prs: list[PullRequest]): t = rich.table.Table( title=f'{issue.key} -- {issue.summary}', caption_justify='left', ) t.add_column("Repository", no_wrap=True) t.add_column("Branch", no_wrap=True) t.add_column("Pull Request") for it in prs: t.add_row(it.repository, it.branch, it.name) console.print(t) def main(): logging.basicConfig(level=logging.INFO) if not JIRA_EMAIL: logging.error('JIRA_EMAIL is not set') exit(1) if not JIRA_TOKEN: logging.error('JIRA_TOKEN is not set') exit(1) j = Jira(email=JIRA_EMAIL, token=JIRA_TOKEN) logging.info('fetching issues in "ready to release" status') issues = j.list_issues_for_release() for it in issues: logging.info(f'fetching pull requests for issue {it.key}') prs = j.get_approved_prs(it.id) if not prs: logging.info(f'no non-merged pull requests found for issue {it.key}') continue render_issue(it, prs) if __name__ == '__main__': main()