"""Jira integration service — wraps atlassian-python-api for Jira REST calls.""" import logging from datetime import datetime from typing import Optional from sqlalchemy.orm import Session from app.config import settings from app.domain.exceptions import InvalidOperationError from app.models.jira_link import JiraLink logger = logging.getLogger(__name__) _jira_client = None def get_jira_client(): """Return a lazily-initialised Jira client, or raise if disabled.""" global _jira_client if not settings.JIRA_ENABLED: raise InvalidOperationError("Jira integration is not enabled") if _jira_client is None: from atlassian import Jira _jira_client = Jira( url=settings.JIRA_URL, username=settings.JIRA_USERNAME, password=settings.JIRA_API_TOKEN, cloud=settings.JIRA_IS_CLOUD, ) return _jira_client def search_jira_issues(query: str, max_results: int = 10) -> list[dict]: """Search Jira issues by JQL or free text.""" jira = get_jira_client() jql = query if "=" in query or "~" in query else f'summary ~ "{query}"' results = jira.jql(jql, limit=max_results) return [ { "issue_key": issue["key"], "summary": issue["fields"]["summary"], "status": issue["fields"]["status"]["name"], "assignee": (issue["fields"].get("assignee") or {}).get("displayName"), "priority": (issue["fields"].get("priority") or {}).get("name"), } for issue in results.get("issues", []) ] def create_jira_issue( project_key: str, summary: str, description: str, issue_type: str = "Task", labels: Optional[list[str]] = None, custom_fields: Optional[dict] = None, ) -> dict: """Create a Jira issue and return its key + id.""" jira = get_jira_client() fields: dict = { "project": {"key": project_key}, "summary": summary, "description": description, "issuetype": {"name": issue_type}, } if labels: fields["labels"] = labels if custom_fields: fields.update(custom_fields) result = jira.issue_create(fields=fields) return {"issue_key": result["key"], "issue_id": result["id"]} def sync_jira_to_aegis(db: Session, link: JiraLink) -> None: """Pull current status from Jira into the local link record.""" jira = get_jira_client() issue = jira.issue(link.jira_issue_key) fields = issue.get("fields", {}) link.jira_status = fields.get("status", {}).get("name") link.jira_priority = (fields.get("priority") or {}).get("name") link.jira_assignee = (fields.get("assignee") or {}).get("displayName") link.jira_story_points = str(fields.get("customfield_10016", "")) link.last_synced_at = datetime.utcnow() db.flush() def sync_aegis_to_jira(db: Session, link: JiraLink, entity_data: dict) -> None: """Push an Aegis status update as a Jira comment.""" jira = get_jira_client() comment_body = _build_sync_comment(entity_data) jira.issue_add_comment(link.jira_issue_key, comment_body) link.last_synced_at = datetime.utcnow() db.flush() def _build_sync_comment(data: dict) -> str: """Build a formatted Jira comment from entity data.""" lines = ["h3. Aegis Sync Update", ""] for key, value in data.items(): lines.append(f"*{key}:* {value}") lines.append(f"\n_Synced at {datetime.utcnow().isoformat()}_") return "\n".join(lines)