Coverage for src/mcp_atlassian/confluence/spaces.py: 88%
52 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 03:26 +0900
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 03:26 +0900
1"""Module for Confluence space operations."""
3import logging
4from typing import cast
6import requests
8from .client import ConfluenceClient
10logger = logging.getLogger("mcp-atlassian")
13class SpacesMixin(ConfluenceClient):
14 """Mixin for Confluence space operations."""
16 def get_spaces(self, start: int = 0, limit: int = 10) -> dict[str, object]:
17 """
18 Get all available spaces.
20 Args:
21 start: The starting index for pagination
22 limit: Maximum number of spaces to return
24 Returns:
25 Dictionary containing space information with results and metadata
26 """
27 spaces = self.confluence.get_all_spaces(start=start, limit=limit)
28 # Cast the return value to the expected type
29 return cast(dict[str, object], spaces)
31 def get_user_contributed_spaces(self, limit: int = 250) -> dict:
32 """
33 Get spaces the current user has contributed to.
35 Args:
36 limit: Maximum number of results to return
38 Returns:
39 Dictionary of space keys to space information
40 """
41 try:
42 # Use CQL to find content the user has contributed to
43 cql = "contributor = currentUser() order by lastmodified DESC"
44 results = self.confluence.cql(cql=cql, limit=limit)
46 # Extract and deduplicate spaces
47 spaces = {}
48 for result in results.get("results", []):
49 space_key = None
50 space_name = None
52 # Try to extract space from container
53 if "resultGlobalContainer" in result:
54 container = result.get("resultGlobalContainer", {})
55 space_name = container.get("title")
56 display_url = container.get("displayUrl", "")
57 if display_url and "/spaces/" in display_url:
58 space_key = display_url.split("/spaces/")[1].split("/")[0]
60 # Try to extract from content expandable
61 if (
62 not space_key
63 and "content" in result
64 and "_expandable" in result["content"]
65 ):
66 expandable = result["content"].get("_expandable", {})
67 space_path = expandable.get("space", "")
68 if space_path and space_path.startswith("/rest/api/space/"):
69 space_key = space_path.split("/rest/api/space/")[1]
71 # Try to extract from URL
72 if not space_key and "url" in result:
73 url = result.get("url", "")
74 if url and url.startswith("/spaces/"):
75 space_key = url.split("/spaces/")[1].split("/")[0]
77 # Only add if we found a space key and it's not already in our results
78 if space_key and space_key not in spaces:
79 # Add some defaults if we couldn't extract all fields
80 space_name = space_name or f"Space {space_key}"
81 spaces[space_key] = {"key": space_key, "name": space_name}
83 return spaces
85 except KeyError as e:
86 logger.error(f"Missing key in Confluence spaces data: {str(e)}")
87 return {}
88 except ValueError as e:
89 logger.error(f"Invalid value in Confluence spaces: {str(e)}")
90 return {}
91 except TypeError as e:
92 logger.error(f"Type error when processing Confluence spaces: {str(e)}")
93 return {}
94 except requests.RequestException as e:
95 logger.error(f"Network error when fetching spaces: {str(e)}")
96 return {}
97 except Exception as e: # noqa: BLE001 - Intentional fallback with logging
98 logger.error(f"Unexpected error fetching Confluence spaces: {str(e)}")
99 logger.debug("Full exception details for Confluence spaces:", exc_info=True)
100 return {}