Coverage for mindsdb / integrations / handlers / jira_handler / jira_tables.py: 0%

104 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-21 00:36 +0000

1from typing import List, Optional 

2 

3from atlassian import Jira 

4import pandas as pd 

5 

6from mindsdb.integrations.libs.api_handler import APIResource 

7from mindsdb.integrations.utilities.sql_utils import FilterCondition, SortColumn, FilterOperator 

8from mindsdb.utilities import log 

9 

10 

11logger = log.getLogger(__name__) 

12 

13 

14class JiraTableBase(APIResource): 

15 """Base class for Jira tables""" 

16 

17 def to_dataframe(self, records: Optional[List[dict]]) -> pd.DataFrame: 

18 """ 

19 Convert records to DataFrame with fixed columns, handling missing optional fields. 

20 

21 Args: 

22 records: List of record dictionaries from Jira API, or None/empty list 

23 

24 Returns: 

25 DataFrame with all expected columns, missing fields filled with None 

26 """ 

27 if records: 

28 df = pd.DataFrame(records) 

29 df = df.reindex(columns=self.get_columns(), fill_value=None) 

30 else: 

31 df = pd.DataFrame([], columns=self.get_columns()) 

32 return df 

33 

34 

35class JiraProjectsTable(JiraTableBase): 

36 def list( 

37 self, 

38 conditions: Optional[List[FilterCondition]] = None, 

39 limit: Optional[int] = None, 

40 sort: Optional[List[SortColumn]] = None, 

41 targets: Optional[List[str]] = None, 

42 **kwargs, 

43 ) -> pd.DataFrame: 

44 client: Jira = self.handler.connect() 

45 

46 projects = [] 

47 conditions = conditions or [] 

48 for condition in conditions: 

49 if condition.column in ("id", "key"): 

50 if condition.op == FilterOperator.EQUAL: 

51 projects = [client.get_project(condition.value)] 

52 elif condition.op == FilterOperator.IN: 

53 projects = [client.get_project(project_id) for project_id in condition.value] 

54 else: 

55 raise ValueError(f"Unsupported operator {condition.op} for column {condition.column}.") 

56 condition.applied = True 

57 

58 if not projects: 

59 projects = client.get_all_projects() 

60 

61 return self.to_dataframe(projects) 

62 

63 def get_columns(self) -> List[str]: 

64 return [ 

65 "id", 

66 "key", 

67 "name", 

68 "projectTypeKey", 

69 "simplified", 

70 "style", 

71 "isPrivate", 

72 "entityId", 

73 "uuid", 

74 ] 

75 

76 

77class JiraIssuesTable(JiraTableBase): 

78 def list( 

79 self, 

80 conditions: Optional[List[FilterCondition]] = None, 

81 limit: Optional[int] = None, 

82 sort: Optional[List[SortColumn]] = None, 

83 targets: Optional[List[str]] = None, 

84 **kwargs, 

85 ) -> pd.DataFrame: 

86 client: Jira = self.handler.connect() 

87 

88 issues = [] 

89 conditions = conditions or [] 

90 for condition in conditions: 

91 if condition.column in ("id", "key"): 

92 if condition.op == FilterOperator.EQUAL: 

93 issues = [client.get_issue(condition.value)] 

94 elif condition.op == FilterOperator.IN: 

95 issues = [client.get_issue(issue_id) for issue_id in condition.value] 

96 else: 

97 raise ValueError(f"Unsupported operator {condition.op} for column {condition.column}.") 

98 condition.applied = True 

99 

100 elif condition.column in ("project_id", "project_key", "project_name"): 

101 if condition.op == FilterOperator.EQUAL: 

102 issues = client.get_all_project_issues(condition.value, limit=limit) 

103 elif condition.op == FilterOperator.IN: 

104 for project_id in condition.value: 

105 issues.extend(client.get_all_project_issues(project_id, limit=limit)) 

106 

107 condition.applied = True 

108 

109 if not issues: 

110 project_ids = [project["id"] for project in client.get_all_projects()] 

111 for project_id in project_ids: 

112 issues.extend( 

113 self._get_project_issues_with_limit(client, project_id, limit=limit, current_issues=issues) 

114 ) 

115 

116 if issues: 

117 return self.normalize(issues) 

118 else: 

119 return self.to_dataframe(issues) 

120 

121 def _get_project_issues_with_limit( 

122 self, 

123 client: Jira, 

124 project_id: str, 

125 limit: Optional[int] = None, 

126 current_issues: Optional[List] = None, 

127 ): 

128 """ 

129 Helper to get issues from a project, respecting the limit. 

130 """ 

131 if current_issues is None: 

132 current_issues = [] 

133 if limit: 

134 remaining = limit - len(current_issues) 

135 if remaining <= 0: 

136 return [] 

137 return client.get_all_project_issues(project_id, limit=remaining) 

138 else: 

139 return client.get_all_project_issues(project_id) 

140 

141 def normalize(self, issues: dict) -> pd.DataFrame: 

142 issues_df = pd.json_normalize(issues) 

143 # Use errors='ignore' to skip columns that don't exist in the data 

144 issues_df.rename( 

145 columns={ 

146 "fields.project.id": "project_id", 

147 "fields.project.key": "project_key", 

148 "fields.project.name": "project_name", 

149 "fields.summary": "summary", 

150 "fields.priority.name": "priority", 

151 "fields.creator.displayName": "creator", 

152 "fields.assignee.displayName": "assignee", 

153 "fields.status.name": "status", 

154 }, 

155 inplace=True, 

156 errors="ignore", 

157 ) 

158 issues_df = issues_df.reindex(columns=self.get_columns(), fill_value=None) 

159 

160 return issues_df 

161 

162 def get_columns(self) -> List[str]: 

163 return [ 

164 "id", 

165 "key", 

166 "project_id", 

167 "project_key", 

168 "project_name", 

169 "summary", 

170 "priority", 

171 "creator", 

172 "assignee", 

173 "status", 

174 ] 

175 

176 

177class JiraGroupsTable(JiraTableBase): 

178 def list( 

179 self, 

180 conditions: Optional[List[FilterCondition]] = None, 

181 limit: Optional[int] = None, 

182 sort: Optional[List[SortColumn]] = None, 

183 targets: Optional[List[str]] = None, 

184 **kwargs, 

185 ) -> pd.DataFrame: 

186 client: Jira = self.handler.connect() 

187 

188 if limit: 

189 groups = client.get_groups(limit=limit)["groups"] 

190 else: 

191 groups = client.get_groups()["groups"] 

192 

193 return self.to_dataframe(groups) 

194 

195 def get_columns(self) -> List[str]: 

196 return [ 

197 "groupId", 

198 "name", 

199 "html", 

200 ] 

201 

202 

203class JiraUsersTable(JiraTableBase): 

204 def list( 

205 self, 

206 conditions: Optional[List[FilterCondition]] = None, 

207 limit: Optional[int] = None, 

208 sort: Optional[List[SortColumn]] = None, 

209 targets: Optional[List[str]] = None, 

210 **kwargs, 

211 ) -> pd.DataFrame: 

212 client: Jira = self.handler.connect() 

213 

214 users = [] 

215 conditions = conditions or [] 

216 for condition in conditions: 

217 if condition.column == "accountId": 

218 if condition.op == FilterOperator.EQUAL: 

219 users = [client.user(account_id=condition.value)] 

220 elif condition.op == FilterOperator.IN: 

221 users = [client.user(account_id=accountId) for accountId in condition.value] 

222 else: 

223 raise ValueError(f"Unsupported operator {condition.op} for column {condition.column}.") 

224 condition.applied = True 

225 

226 if not users: 

227 if limit: 

228 users = client.users_get_all(limit=limit) 

229 else: 

230 users = client.users_get_all() 

231 

232 return self.to_dataframe(users) 

233 

234 def get_columns(self) -> List[str]: 

235 return [ 

236 "accountId", 

237 "accountType", 

238 "emailAddress", 

239 "displayName", 

240 "active", 

241 "timeZone", 

242 "locale", 

243 ]