import logging from atlassian import Jira from sqlmodel import Session from app.models.jira_site import JiraSiteConfig from app.writer.tools.github import fetch_mention_github_issues from app.libs.adf.convertor import convert_markdown_to_adf_document from app.libs.confluence.confluence import ( PageInitialStatus, PageRepresentation, PresetContentStates, Confluence, ) from app.models import JiraSite, JiraIssue, JiraProject, ConfluencePage, ConfluenceSite from app.models.common import DeploymentType from app.models.kb_issue_case import KBIssueCase from app.models.kb_generate_task import KBGenerateConfig from app.models.support_ticket import SupportTicket, TicketComment from app.repositories.confluence_page import confluence_page_repo from app.repositories.confluence_site import confluence_site_repo from app.repositories.confluence_space import confluence_space_repo from app.repositories.jira_site import jira_site_repo logger = logging.getLogger(__name__) def create_jira_instance_by_site(site: JiraSite): if site.deployment_type == DeploymentType.CLOUD: return Jira( url=f"https://{site.host}", username=site.bot_user_id, password=site.bot_user_token, cloud=True, ) else: return Jira(url=f"https://{site.host}", token=site.bot_user_token, cloud=False) def load_jira_issue( session: Session, site_id: int, issue_key: str, site_config: JiraSiteConfig ) -> JiraIssue: site = jira_site_repo.must_get(session, site_id) jira = create_jira_instance_by_site(site) raw_issue = jira.issue(issue_key) fields = raw_issue["fields"] # Jira Project project_guid = f"SITE-{site_id}-PROJECT-{fields['project']['id']}" project = JiraProject( guid=project_guid, id=fields["project"]["id"], key=fields["project"]["key"], name=fields["project"]["name"], site_id=site_id, ) project = session.merge(project) # Jira Issue issue_guid = f"SITE-{site_id}-ISSUE-{raw_issue['id']}" issue_link = f"https://{site.host}/browse/{raw_issue['key']}" issue = JiraIssue( guid=issue_guid, id=raw_issue["id"], key=raw_issue["key"], link=issue_link, summary=fields["summary"], kb_issue_case_link=fields[site_config.kb_issue_case_link_field_key] if site_config.kb_issue_case_link_field_key else None, fields=fields, project_guid=project_guid, project_id=fields["project"]["id"], project_key=fields["project"]["key"], project=project, site_id=site_id, ) issue = session.merge(issue) session.commit() return issue def jira_issue_to_support_ticket( issue: JiraIssue, config: KBGenerateConfig ) -> SupportTicket: fields = issue.fields description = fields["description"] comments = [ TicketComment( body=comment["body"], # Notice: The API of self-Hosted Jira doesn't provide jsdPublic field. internal=comment["jsdPublic"] if "jsdPublic" in comment else None, author=comment["author"]["displayName"], created=comment["created"], updated_author=comment["updateAuthor"]["displayName"], updated=comment["updated"], ) for comment in fields["comment"]["comments"] ] draft_root_cause = ( fields[config.root_cause_field_key] if config.root_cause_field_key else None ) draft_solution = ( fields[config.solution_field_key] if config.solution_field_key else None ) draft_workaround = ( fields[config.workaround_field_key] if config.workaround_field_key else None ) # TODO: using fetch github issue tool instead. texts = [description, draft_root_cause, draft_solution, draft_workaround] texts.extend([comment.body for comment in comments]) mention_github_issues = fetch_mention_github_issues(texts) return SupportTicket( key=issue.key, link=issue.link, title=issue.summary, reporter=fields["reporter"]["displayName"], description=description, versions=map_jira_field_to_names(fields["versions"]), components=map_jira_field_to_names(fields["components"]), comments=comments, draft_root_cause=draft_root_cause, draft_solution=draft_solution, draft_workaround=draft_workaround, mention_github_issues=mention_github_issues, ) def map_jira_field_to_names(field: list[dict]) -> list[str]: return [item["name"] for item in field] def create_confluence_by_site(site: ConfluenceSite) -> Confluence: # TODO: support self-hosted confluence. return Confluence( url=f"https://{site.host}", username=site.bot_user_id, password=site.bot_user_token, cloud=site.deployment_type == DeploymentType.CLOUD, ) def create_confluence_page_for_kb_document( session: Session, doc: KBIssueCase, config: KBGenerateConfig, ) -> ConfluencePage: assert config.target_confluence_site_id is not None site = confluence_site_repo.must_get(session, config.target_confluence_site_id) confluence = create_confluence_by_site(site) # Check if the target space exists. assert config.target_confluence_space_id is not None space = confluence.get_space_by_id(config.target_confluence_space_id) db_space = confluence_space_repo.find_or_create(session, site, space) # Create a draft page. adf_doc = convert_markdown_to_adf_document(doc.body) adf_dict = adf_doc.model_dump_json(exclude_none=True) page = confluence.create_new_page( space_id=db_space.id, title=doc.title, body=adf_dict, status=PageInitialStatus.CURRENT, representation=PageRepresentation.ATLAS_DOC_FORMAT, parent_id=config.target_confluence_parent_page_id, ) try: db_page = confluence_page_repo.create_page(session, site, db_space, page) logger.info(f"Created confluence page for KB document: %s", db_page.link) # Set labels for the page. if len(doc.labels) > 0: labels = doc.labels[:20] if len(doc.labels) > 20: logger.warning( f"The generated KB documents has more than 20 labels, can not add all of them for page #{db_page.id}." ) confluence.add_content_labels( page["id"], [{"name": label} for label in labels] ) db_page.labels = labels confluence_page_repo.udpate_page(session, db_page) # Publish the page and set the content state to rough draft. confluence.set_content_state_with_preset( page["id"], PresetContentStates.ROUGH_DRAFT ) except Exception as e: logger.error( "Failed to finished the confluence page creation, revert the page creation." ) try: confluence.delete_page(page["id"]) except Exception as e2: logger.error("Failed to revert the created confluence page.") raise e2 raise e else: return db_page