from __future__ import annotations import base64 import logging import httpx from yuxi.channel.protocols import ChannelAccountSnapshot, StatusProtocol logger = logging.getLogger(__name__) class JiraStatus(StatusProtocol): default_runtime = ChannelAccountSnapshot(account_id="default") async def probe(self, account: dict | None = None) -> bool: if not account: return False try: info = await JiraStatus.get_account_info(account) return info is not None and info.get("accountId") is not None except Exception: return False def build_summary(self, snapshot: object) -> dict: return {"channel": "jira"} def build_channel_summary( self, account: dict, config: dict, default_account_id: str, snapshot: ChannelAccountSnapshot, ) -> dict: return { "channel": "jira", "account_id": account.get("account_id", default_account_id), "configured": bool(account.get("site_url") and account.get("email") and account.get("api_token")), "site_url": account.get("site_url", ""), "project_key": account.get("project_key", ""), "running": snapshot.running, "connected": snapshot.connected, } def format_capabilities_probe(self, probe: object) -> list[dict]: return [] async def audit_account(self, account: dict, timeout_ms: int, config: dict, probe: object | None = None) -> object: return {"ok": True} async def build_capabilities_diagnostics( self, account: dict, timeout_ms: int, config: dict, probe: object | None = None, audit: object | None = None, target: str | None = None, ) -> dict | None: return None def build_account_snapshot( self, account: dict, config: dict, runtime: ChannelAccountSnapshot | None = None, probe: object | None = None, audit: object | None = None, ) -> ChannelAccountSnapshot: return ChannelAccountSnapshot( account_id=account.get("account_id", ""), configured=bool(account.get("site_url") and account.get("email") and account.get("api_token")), enabled=account.get("enabled", True) if isinstance(account.get("enabled"), bool) else True, probe=probe, audit=audit, ) def log_self_id(self, account: dict, config: dict, runtime: object, include_channel_prefix: bool = False) -> None: pass def resolve_account_state( self, account: dict, config: dict, configured: bool, enabled: bool, ) -> str: if not configured: return "unconfigured" if not enabled: return "disabled" return "ready" def collect_status_issues(self, accounts: list[ChannelAccountSnapshot]) -> list: return [] @staticmethod async def probe_account(account: dict) -> bool: try: info = await JiraStatus.get_account_info(account) return info is not None and info.get("accountId") is not None except Exception: return False @staticmethod async def get_account_info(account: dict) -> dict | None: site_url = account.get("site_url", "") email = account.get("email", "") api_token = account.get("api_token", "") if not all([site_url, email, api_token]): return None credentials = f"{email}:{api_token}" auth = f"Basic {base64.b64encode(credentials.encode('utf-8')).decode('utf-8')}" try: async with httpx.AsyncClient(timeout=httpx.Timeout(10.0)) as client: resp = await client.get( f"{site_url}/rest/api/3/myself", headers={"Authorization": auth, "Accept": "application/json"}, ) if resp.status_code == 200: data = resp.json() return { "accountId": data.get("accountId", ""), "displayName": data.get("displayName", ""), "emailAddress": data.get("emailAddress", ""), "active": data.get("active", False), } except Exception: logger.exception("Jira account info probe failed") return None @staticmethod async def check_webhook_status(account: dict) -> dict | None: site_url = account.get("site_url", "") email = account.get("email", "") api_token = account.get("api_token", "") webhook_id = account.get("webhook_id", "") if not all([site_url, email, api_token]): return None credentials = f"{email}:{api_token}" auth = f"Basic {base64.b64encode(credentials.encode('utf-8')).decode('utf-8')}" try: async with httpx.AsyncClient(timeout=httpx.Timeout(10.0)) as client: if webhook_id: resp = await client.get( f"{site_url}/rest/api/3/webhook/{webhook_id}", headers={"Authorization": auth}, ) if resp.status_code == 200: return resp.json() resp = await client.get( f"{site_url}/rest/api/3/webhook", headers={"Authorization": auth}, ) if resp.status_code == 200: webhooks = resp.json() for wh in webhooks: callback = wh.get("url", "") if account.get("callback_base_url", "") in callback: return wh except Exception: logger.exception("Jira webhook status check failed") return None @staticmethod def build_summary_data(account: dict, user_info: dict | None = None) -> dict: summary: dict = { "account_id": account.get("account_id", ""), "configured": bool(account.get("site_url") and account.get("email") and account.get("api_token")), "site_url": account.get("site_url", ""), "project_key": account.get("project_key", ""), "comment_visibility": account.get("comment_visibility", "internal"), } if user_info: summary["bot_account_id"] = user_info.get("accountId", "") summary["display_name"] = user_info.get("displayName", "") summary["active"] = user_info.get("active", False) return summary