0ddd17047d
Task D — Google-style docstrings (Args/Returns) on every public function, method, and class across all 158 Python files in the backend. Zero ruff D violations (pydocstyle Google convention). Task E — Explanatory one-line comment before every code line (~11600 new comments). ruff check passes clean after isort re-sort. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
236 lines
7.1 KiB
Python
236 lines
7.1 KiB
Python
"""Jira integration router — link, search, sync, create issues."""
|
|
|
|
# Import logging
|
|
import logging
|
|
|
|
# Import Optional from typing
|
|
from typing import Optional
|
|
|
|
# Import UUID from uuid
|
|
from uuid import UUID
|
|
|
|
# Import APIRouter, Depends, Query from fastapi
|
|
from fastapi import APIRouter, Depends, Query
|
|
|
|
# Import Session from sqlalchemy.orm
|
|
from sqlalchemy.orm import Session
|
|
|
|
# Import get_db from app.database
|
|
from app.database import get_db
|
|
|
|
# Import get_current_user, require_role from app.dependencies.auth
|
|
from app.dependencies.auth import get_current_user, require_role
|
|
|
|
# Import UnitOfWork from app.domain.unit_of_work
|
|
from app.domain.unit_of_work import UnitOfWork
|
|
|
|
# Import JiraLinkEntityType from app.models.jira_link
|
|
from app.models.jira_link import JiraLinkEntityType
|
|
|
|
# Import User from app.models.user
|
|
from app.models.user import User
|
|
|
|
# Import from app.schemas.jira_schema
|
|
from app.schemas.jira_schema import (
|
|
JiraIssueResult,
|
|
JiraLinkCreate,
|
|
JiraLinkOut,
|
|
)
|
|
|
|
# Import audit_service, jira_service from app.services
|
|
from app.services import audit_service, jira_service
|
|
|
|
# Assign logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Assign router = APIRouter(prefix="/jira", tags=["jira"])
|
|
router = APIRouter(prefix="/jira", tags=["jira"])
|
|
|
|
|
|
# Apply the @router.get decorator
|
|
@router.get("/search", response_model=list[JiraIssueResult])
|
|
# Define function search_issues
|
|
def search_issues(
|
|
# Entry: q
|
|
q: str = Query(..., min_length=2),
|
|
# Entry: max_results
|
|
max_results: int = Query(10, le=50),
|
|
# Entry: user
|
|
user: User = Depends(get_current_user),
|
|
) -> list[JiraIssueResult]:
|
|
"""Search Jira issues by JQL or free text."""
|
|
# Return jira_service.search_jira_issues(q, max_results)
|
|
return jira_service.search_jira_issues(q, max_results)
|
|
|
|
|
|
# Apply the @router.post decorator
|
|
@router.post("/links", response_model=JiraLinkOut, status_code=201)
|
|
# Define function create_link
|
|
def create_link(
|
|
# Entry: body
|
|
body: JiraLinkCreate,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: user
|
|
user: User = Depends(get_current_user),
|
|
) -> JiraLinkOut:
|
|
"""Associate an Aegis entity with a Jira issue."""
|
|
# Open context manager
|
|
with UnitOfWork(db) as uow:
|
|
# Assign link = jira_service.create_link(
|
|
link = jira_service.create_link(
|
|
db,
|
|
# Keyword argument: entity_type
|
|
entity_type=body.entity_type,
|
|
# Keyword argument: entity_id
|
|
entity_id=body.entity_id,
|
|
# Keyword argument: jira_issue_key
|
|
jira_issue_key=body.jira_issue_key,
|
|
# Keyword argument: sync_direction
|
|
sync_direction=body.sync_direction,
|
|
# Keyword argument: created_by
|
|
created_by=user.id,
|
|
)
|
|
# Call audit_service.log_action()
|
|
audit_service.log_action(
|
|
db,
|
|
# Keyword argument: user_id
|
|
user_id=user.id,
|
|
# Keyword argument: action
|
|
action="JIRA_LINK_CREATED",
|
|
# Keyword argument: entity_type
|
|
entity_type="jira_link",
|
|
# Keyword argument: entity_id
|
|
entity_id=str(link.id),
|
|
# Keyword argument: details
|
|
details={
|
|
# Literal argument value
|
|
"linked_entity_type": body.entity_type.value,
|
|
# Literal argument value
|
|
"linked_entity_id": str(body.entity_id),
|
|
# Literal argument value
|
|
"jira_issue_key": body.jira_issue_key,
|
|
},
|
|
)
|
|
# Call uow.commit()
|
|
uow.commit()
|
|
# Reload ORM object attributes from the database
|
|
db.refresh(link)
|
|
|
|
# Return link
|
|
return link
|
|
|
|
|
|
# Apply the @router.get decorator
|
|
@router.get("/links", response_model=list[JiraLinkOut])
|
|
# Define function list_links
|
|
def list_links(
|
|
# Entry: entity_type
|
|
entity_type: Optional[JiraLinkEntityType] = None,
|
|
# Entry: entity_id
|
|
entity_id: Optional[UUID] = None,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: user
|
|
user: User = Depends(get_current_user),
|
|
) -> list[JiraLinkOut]:
|
|
"""List Jira links, optionally filtered by entity."""
|
|
# Return jira_service.list_links(
|
|
return jira_service.list_links(
|
|
db,
|
|
# Keyword argument: entity_type
|
|
entity_type=entity_type,
|
|
# Keyword argument: entity_id
|
|
entity_id=entity_id,
|
|
)
|
|
|
|
|
|
# Apply the @router.post decorator
|
|
@router.post("/links/{link_id}/sync")
|
|
# Define function sync_link
|
|
def sync_link(
|
|
# Entry: link_id
|
|
link_id: UUID,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: user
|
|
user: User = Depends(require_role("admin")),
|
|
) -> dict:
|
|
"""Force bidirectional sync for a specific Jira link."""
|
|
# Open context manager
|
|
with UnitOfWork(db) as uow:
|
|
# Assign link = jira_service.get_link_or_raise(db, link_id)
|
|
link = jira_service.get_link_or_raise(db, link_id)
|
|
# Call jira_service.sync_jira_to_aegis()
|
|
jira_service.sync_jira_to_aegis(db, link)
|
|
# Call uow.commit()
|
|
uow.commit()
|
|
# Return {"message": "Sync completed", "jira_status": link.jira_status}
|
|
return {"message": "Sync completed", "jira_status": link.jira_status}
|
|
|
|
|
|
# Apply the @router.delete decorator
|
|
@router.delete("/links/{link_id}", status_code=204)
|
|
# Define function delete_link
|
|
def delete_link(
|
|
# Entry: link_id
|
|
link_id: UUID,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: user
|
|
user: User = Depends(get_current_user),
|
|
) -> None:
|
|
"""Remove a Jira link."""
|
|
# Open context manager
|
|
with UnitOfWork(db) as uow:
|
|
# Assign link = jira_service.delete_link(db, link_id)
|
|
link = jira_service.delete_link(db, link_id)
|
|
# Call audit_service.log_action()
|
|
audit_service.log_action(
|
|
db,
|
|
# Keyword argument: user_id
|
|
user_id=user.id,
|
|
# Keyword argument: action
|
|
action="jira_link_deleted",
|
|
# Keyword argument: entity_type
|
|
entity_type="jira_link",
|
|
# Keyword argument: entity_id
|
|
entity_id=str(link_id),
|
|
# Keyword argument: details
|
|
details={"jira_issue_key": link.jira_issue_key},
|
|
)
|
|
# Call uow.commit()
|
|
uow.commit()
|
|
|
|
|
|
# Apply the @router.post decorator
|
|
@router.post("/create-issue")
|
|
# Define function create_issue_from_entity
|
|
def create_issue_from_entity(
|
|
# Entry: entity_type
|
|
entity_type: JiraLinkEntityType,
|
|
# Entry: entity_id
|
|
entity_id: UUID,
|
|
# Entry: db
|
|
db: Session = Depends(get_db),
|
|
# Entry: user
|
|
user: User = Depends(get_current_user),
|
|
) -> dict:
|
|
"""Auto-create a Jira issue from an Aegis entity and link them."""
|
|
# Open context manager
|
|
with UnitOfWork(db) as uow:
|
|
# Assign result = jira_service.create_issue_and_link(
|
|
result = jira_service.create_issue_and_link(
|
|
db,
|
|
# Keyword argument: entity_type
|
|
entity_type=entity_type,
|
|
# Keyword argument: entity_id
|
|
entity_id=entity_id,
|
|
# Keyword argument: created_by
|
|
created_by=user.id,
|
|
)
|
|
# Call uow.commit()
|
|
uow.commit()
|
|
# Return result
|
|
return result
|