Coverage for src/mcp_atlassian/jira/comments.py: 91%

45 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-10 03:26 +0900

1"""Module for Jira comment operations.""" 

2 

3import logging 

4from typing import Any, Dict, List, Optional 

5 

6import requests 

7 

8from ..document_types import Document 

9from .client import JiraClient 

10 

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

12 

13 

14class CommentsMixin(JiraClient): 

15 """Mixin for Jira comment operations.""" 

16 

17 def get_issue_comments(self, issue_key: str, limit: int = 50) -> List[Dict[str, Any]]: 

18 """ 

19 Get comments for a specific issue. 

20 

21 Args: 

22 issue_key: The issue key (e.g. 'PROJ-123') 

23 limit: Maximum number of comments to return 

24 

25 Returns: 

26 List of comments with author, creation date, and content 

27 

28 Raises: 

29 Exception: If there is an error getting comments 

30 """ 

31 try: 

32 comments = self.jira.issue_get_comments(issue_key) 

33 processed_comments = [] 

34 

35 for comment in comments.get("comments", [])[:limit]: 

36 processed_comment = { 

37 "id": comment.get("id"), 

38 "body": self._clean_text(comment.get("body", "")), 

39 "created": self._parse_date(comment.get("created")), 

40 "updated": self._parse_date(comment.get("updated")), 

41 "author": comment.get("author", {}).get("displayName", "Unknown"), 

42 } 

43 processed_comments.append(processed_comment) 

44 

45 return processed_comments 

46 except Exception as e: 

47 logger.error(f"Error getting comments for issue {issue_key}: {str(e)}") 

48 raise Exception(f"Error getting comments: {str(e)}") from e 

49 

50 def add_comment(self, issue_key: str, comment: str) -> Dict[str, Any]: 

51 """ 

52 Add a comment to an issue. 

53 

54 Args: 

55 issue_key: The issue key (e.g. 'PROJ-123') 

56 comment: Comment text to add (in Markdown format) 

57 

58 Returns: 

59 The created comment details 

60 

61 Raises: 

62 Exception: If there is an error adding the comment 

63 """ 

64 try: 

65 # Convert Markdown to Jira's markup format 

66 jira_formatted_comment = self._markdown_to_jira(comment) 

67 

68 result = self.jira.issue_add_comment(issue_key, jira_formatted_comment) 

69 return { 

70 "id": result.get("id"), 

71 "body": self._clean_text(result.get("body", "")), 

72 "created": self._parse_date(result.get("created")), 

73 "author": result.get("author", {}).get("displayName", "Unknown"), 

74 } 

75 except Exception as e: 

76 logger.error(f"Error adding comment to issue {issue_key}: {str(e)}") 

77 raise Exception(f"Error adding comment: {str(e)}") from e 

78 

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

80 """ 

81 Convert Markdown syntax to Jira markup syntax. 

82 

83 This method uses the TextPreprocessor implementation for consistent 

84 conversion between Markdown and Jira markup. 

85 

86 Args: 

87 markdown_text: Text in Markdown format 

88 

89 Returns: 

90 Text in Jira markup format 

91 """ 

92 if not markdown_text: 

93 return "" 

94 

95 # Use the existing preprocessor 

96 try: 

97 return self.preprocessor.markdown_to_jira(markdown_text) 

98 except Exception as e: 

99 logger.warning(f"Error converting markdown to Jira format: {str(e)}") 

100 # Return the original text if conversion fails 

101 return markdown_text 

102 

103 def _parse_date(self, date_str: Optional[str]) -> str: 

104 """ 

105 Parse a date string from ISO format to a more readable format. 

106  

107 This method is included for independence from other mixins, 

108 but will use the implementation from other mixins if available. 

109  

110 Args: 

111 date_str: Date string in ISO format or None 

112  

113 Returns: 

114 Formatted date string or empty string if date_str is None 

115 """ 

116 # If the date string is None, return empty string 

117 if date_str is None: 

118 return "" 

119 

120 # If another mixin has implemented this method, use that implementation 

121 if hasattr(self, '_parse_date') and self.__class__._parse_date is not CommentsMixin._parse_date: 

122 # This avoids infinite recursion by checking that the method is different 

123 return super()._parse_date(date_str) 

124 

125 # Fallback implementation 

126 try: 

127 from datetime import datetime 

128 date_obj = datetime.fromisoformat(date_str.replace("Z", "+00:00")) 

129 return date_obj.strftime("%Y-%m-%d") 

130 except (ValueError, TypeError): 

131 return date_str