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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 00:36 +0000
1from typing import List, Optional
3from atlassian import Jira
4import pandas as pd
6from mindsdb.integrations.libs.api_handler import APIResource
7from mindsdb.integrations.utilities.sql_utils import FilterCondition, SortColumn, FilterOperator
8from mindsdb.utilities import log
11logger = log.getLogger(__name__)
14class JiraTableBase(APIResource):
15 """Base class for Jira tables"""
17 def to_dataframe(self, records: Optional[List[dict]]) -> pd.DataFrame:
18 """
19 Convert records to DataFrame with fixed columns, handling missing optional fields.
21 Args:
22 records: List of record dictionaries from Jira API, or None/empty list
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
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()
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
58 if not projects:
59 projects = client.get_all_projects()
61 return self.to_dataframe(projects)
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 ]
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()
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
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))
107 condition.applied = True
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 )
116 if issues:
117 return self.normalize(issues)
118 else:
119 return self.to_dataframe(issues)
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)
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)
160 return issues_df
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 ]
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()
188 if limit:
189 groups = client.get_groups(limit=limit)["groups"]
190 else:
191 groups = client.get_groups()["groups"]
193 return self.to_dataframe(groups)
195 def get_columns(self) -> List[str]:
196 return [
197 "groupId",
198 "name",
199 "html",
200 ]
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()
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
226 if not users:
227 if limit:
228 users = client.users_get_all(limit=limit)
229 else:
230 users = client.users_get_all()
232 return self.to_dataframe(users)
234 def get_columns(self) -> List[str]:
235 return [
236 "accountId",
237 "accountType",
238 "emailAddress",
239 "displayName",
240 "active",
241 "timeZone",
242 "locale",
243 ]