Skip to content

Commit 2e0802c

Browse files
authored
Merge pull request #59 from natifridman/issue-link
Add support for Jira issue links
2 parents 7f5f67e + 974c5e3 commit 2e0802c

File tree

2 files changed

+159
-3
lines changed

2 files changed

+159
-3
lines changed

src/database.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,105 @@ async def get_issue_comments(issue_ids: List[str], snowflake_token: Optional[str
263263
logger.error(f"Error fetching comments: {str(e)}")
264264

265265
return comments_data
266+
267+
268+
async def get_issue_links(issue_ids: List[str], snowflake_token: Optional[str] = None) -> Dict[str, List[Dict[str, Any]]]:
269+
"""Get issue links for given issue IDs from Snowflake"""
270+
if not issue_ids:
271+
return {}
272+
273+
links_data = {}
274+
275+
try:
276+
# Sanitize and validate issue IDs (should be numeric)
277+
sanitized_ids = []
278+
for issue_id in issue_ids:
279+
# Ensure issue IDs are numeric to prevent injection
280+
if isinstance(issue_id, (str, int)) and str(issue_id).isdigit():
281+
sanitized_ids.append(str(issue_id))
282+
283+
if not sanitized_ids:
284+
return {}
285+
286+
# Create comma-separated list for IN clause
287+
ids_str = "'" + "','".join(sanitized_ids) + "'"
288+
289+
sql = f"""
290+
SELECT
291+
il.ID as LINK_ID,
292+
il.SOURCE,
293+
il.DESTINATION,
294+
il.SEQUENCE,
295+
ilt.LINKNAME,
296+
ilt.INWARD,
297+
ilt.OUTWARD,
298+
si.ISSUE_KEY as SOURCE_KEY,
299+
di.ISSUE_KEY as DESTINATION_KEY,
300+
si.SUMMARY as SOURCE_SUMMARY,
301+
di.SUMMARY as DESTINATION_SUMMARY
302+
FROM {SNOWFLAKE_DATABASE}.{SNOWFLAKE_SCHEMA}.JIRA_ISSUELINK_RHAI il
303+
JOIN {SNOWFLAKE_DATABASE}.{SNOWFLAKE_SCHEMA}.JIRA_ISSUELINKTYPE_RHAI ilt
304+
ON il.LINKTYPE = ilt.ID
305+
LEFT JOIN {SNOWFLAKE_DATABASE}.{SNOWFLAKE_SCHEMA}.JIRA_ISSUE_NON_PII si
306+
ON il.SOURCE = si.ID
307+
LEFT JOIN {SNOWFLAKE_DATABASE}.{SNOWFLAKE_SCHEMA}.JIRA_ISSUE_NON_PII di
308+
ON il.DESTINATION = di.ID
309+
WHERE (il.SOURCE IN ({ids_str}) OR il.DESTINATION IN ({ids_str}))
310+
ORDER BY il.SOURCE, il.SEQUENCE
311+
"""
312+
313+
rows = await execute_snowflake_query(sql, snowflake_token)
314+
columns = [
315+
"LINK_ID", "SOURCE", "DESTINATION", "SEQUENCE", "LINKNAME",
316+
"INWARD", "OUTWARD", "SOURCE_KEY", "DESTINATION_KEY",
317+
"SOURCE_SUMMARY", "DESTINATION_SUMMARY"
318+
]
319+
320+
for row in rows:
321+
row_dict = format_snowflake_row(row, columns)
322+
source_id = str(row_dict.get("SOURCE"))
323+
destination_id = str(row_dict.get("DESTINATION"))
324+
325+
# Create link object
326+
link = {
327+
"link_id": row_dict.get("LINK_ID"),
328+
"source_id": source_id,
329+
"destination_id": destination_id,
330+
"sequence": row_dict.get("SEQUENCE"),
331+
"link_type": row_dict.get("LINKNAME"),
332+
"inward_description": row_dict.get("INWARD"),
333+
"outward_description": row_dict.get("OUTWARD"),
334+
"source_key": row_dict.get("SOURCE_KEY"),
335+
"destination_key": row_dict.get("DESTINATION_KEY"),
336+
"source_summary": row_dict.get("SOURCE_SUMMARY"),
337+
"destination_summary": row_dict.get("DESTINATION_SUMMARY")
338+
}
339+
340+
# Add to both source and destination issue data
341+
for issue_id in [source_id, destination_id]:
342+
if issue_id in sanitized_ids:
343+
if issue_id not in links_data:
344+
links_data[issue_id] = []
345+
346+
# Determine relationship direction for this issue
347+
if issue_id == source_id:
348+
link_copy = link.copy()
349+
link_copy["relationship"] = "outward"
350+
link_copy["related_issue_id"] = destination_id
351+
link_copy["related_issue_key"] = row_dict.get("DESTINATION_KEY")
352+
link_copy["related_issue_summary"] = row_dict.get("DESTINATION_SUMMARY")
353+
link_copy["relationship_description"] = row_dict.get("OUTWARD")
354+
else:
355+
link_copy = link.copy()
356+
link_copy["relationship"] = "inward"
357+
link_copy["related_issue_id"] = source_id
358+
link_copy["related_issue_key"] = row_dict.get("SOURCE_KEY")
359+
link_copy["related_issue_summary"] = row_dict.get("SOURCE_SUMMARY")
360+
link_copy["relationship_description"] = row_dict.get("INWARD")
361+
362+
links_data[issue_id].append(link_copy)
363+
364+
except Exception as e:
365+
logger.error(f"Error fetching issue links: {str(e)}")
366+
367+
return links_data

src/tools.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
format_snowflake_row,
1010
sanitize_sql_value,
1111
get_issue_labels,
12-
get_issue_comments
12+
get_issue_comments,
13+
get_issue_links
1314
)
1415
from metrics import track_tool_usage
1516

@@ -151,13 +152,15 @@ async def list_jira_issues(
151152
if row_dict.get("ID"):
152153
issue_ids.append(str(row_dict.get("ID")))
153154

154-
# Get labels for enrichment
155+
# Get labels and links for enrichment
155156
labels_data = await get_issue_labels(issue_ids, snowflake_token)
157+
links_data = await get_issue_links(issue_ids, snowflake_token)
156158

157-
# Enrich issues with labels
159+
# Enrich issues with labels and links
158160
for issue in issues:
159161
issue_id = str(issue['id'])
160162
issue['labels'] = labels_data.get(issue_id, [])
163+
issue['links'] = links_data.get(issue_id, [])
161164

162165
return {
163166
"issues": issues,
@@ -258,6 +261,10 @@ async def get_jira_issue_details(issue_key: str) -> Dict[str, Any]:
258261
comments_data = await get_issue_comments([str(issue['id'])], snowflake_token)
259262
issue['comments'] = comments_data.get(str(issue['id']), [])
260263

264+
# Get issue links for this issue
265+
links_data = await get_issue_links([str(issue['id'])], snowflake_token)
266+
issue['links'] = links_data.get(str(issue['id']), [])
267+
261268
return issue
262269

263270
except Exception as e:
@@ -437,3 +444,50 @@ async def list_jira_components(
437444

438445
except Exception as e:
439446
return {"error": f"Error reading components from Snowflake: {str(e)}", "components": []}
447+
448+
@mcp.tool()
449+
@track_tool_usage("get_jira_issue_links")
450+
async def get_jira_issue_links(issue_key: str) -> Dict[str, Any]:
451+
"""
452+
Get issue links for a specific JIRA issue by its key from Snowflake.
453+
454+
Args:
455+
issue_key: The JIRA issue key (e.g., 'SMQE-1280')
456+
457+
Returns:
458+
Dictionary containing issue links information
459+
"""
460+
try:
461+
# Get the Snowflake token
462+
snowflake_token = get_snowflake_token(mcp)
463+
if not snowflake_token:
464+
return {"error": "Snowflake token not available"}
465+
466+
# First get the issue ID from the issue key
467+
sql = f"""
468+
SELECT ID
469+
FROM JIRA_ISSUE_NON_PII
470+
WHERE ISSUE_KEY = '{sanitize_sql_value(issue_key)}'
471+
LIMIT 1
472+
"""
473+
474+
rows = await execute_snowflake_query(sql, snowflake_token)
475+
476+
if not rows:
477+
return {"error": f"Issue with key '{issue_key}' not found"}
478+
479+
issue_id = str(rows[0][0]) # Get the ID from the first row, first column
480+
481+
# Get issue links for this issue ID
482+
links_data = await get_issue_links([issue_id], snowflake_token)
483+
issue_links = links_data.get(issue_id, [])
484+
485+
return {
486+
"issue_key": issue_key,
487+
"issue_id": issue_id,
488+
"links": issue_links,
489+
"total_links": len(issue_links)
490+
}
491+
492+
except Exception as e:
493+
return {"error": f"Error reading issue links from Snowflake: {str(e)}"}

0 commit comments

Comments
 (0)