Coverage for src/mcp_atlassian/jira/client.py: 89%

1"""Base client module for Jira API interactions."""

2

3 import logging

4 import re

5 from typing import Optional, Dict, Any, cast

6

7 from atlassian import Jira

8

9 from .config import JiraConfig

10

11# Configure logging

12 logger = logging.getLogger("mcp-jira")

13

14

15 class JiraClient:

16 """Base client for Jira API interactions."""

17

18 def __init__(self, config: Optional[JiraConfig] = None) -> None:

19 """Initialize the Jira client with configuration options.

20

21 Args:

22 config: Optional configuration object (will use env vars if not provided)

23

24 Raises:

25 ValueError: If configuration is invalid or required credentials are missing

26 """

27 from mcp_atlassian.preprocessing import TextPreprocessor

28

29 # Load configuration from environment variables if not provided

30 self.config = config or JiraConfig.from_env()

31

32 # Initialize the Jira client based on auth type

33 if self.config.auth_type == "token":

34 self.jira = Jira(

35 url=self.config.url,

36 token=self.config.personal_token,

37 cloud=self.config.is_cloud,

38 verify_ssl=self.config.ssl_verify,

39 )

40 else: # basic auth

41 self.jira = Jira(

42 url=self.config.url,

43 username=self.config.username,

44 password=self.config.api_token,

45 cloud=self.config.is_cloud,

46 verify_ssl=self.config.ssl_verify,

47 )

48

49 # Initialize the text preprocessor for text processing capabilities

50 self.preprocessor = TextPreprocessor(self.config.url)

51

52 # Cache for frequently used data

53 self._field_ids: Optional[Dict[str, str]] = None

54 self._current_user_account_id: Optional[str] = None

55

56 def _clean_text(self, text: str) -> str:

57 """Clean text content by:

58 1. Processing user mentions and links

59 2. Converting HTML/wiki markup to markdown

60

61 Args:

62 text: Text to clean

63

64 Returns:

65 Cleaned text

66 """

67 if not text:

68 return ""

69

70 # Otherwise create a temporary one

71 url = self.config.url if hasattr(self, 'config') else ""

72 return self.preprocessor.clean_jira_text(text)

73

74 def _markdown_to_jira(self, markdown_text: str) -> str:

75 """

76 Convert Markdown syntax to Jira markup syntax.

77

78 Args:

79 markdown_text: Text in Markdown format

80

81 Returns:

82 Text in Jira markup format

83 """

84 if not markdown_text:

85 return ""

86

87 # Use the shared preprocessor if available

88 if hasattr(self, 'preprocessor'):

89 return self.preprocessor.markdown_to_jira(markdown_text)

90

91 # Otherwise create a temporary one

92 url = self.config.url if hasattr(self, 'config') else ""

93 return self.preprocessor.markdown_to_jira(markdown_text)