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

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

2

3 import logging

4 from typing import Any, Dict, List, Optional

5

6 import requests

7

8 from ..document_types import Document

9 from .client import JiraClient

10

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

12

13

14 class 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