Jira toolkit: deduplicate cloud data in Atlassian's available-resources response (#456)
This commit is contained in:
parent
67bc53c8ce
commit
352079151e
3 changed files with 89 additions and 11 deletions
|
|
@ -56,26 +56,29 @@ class JiraClient:
|
||||||
headers={"Authorization": f"Bearer {self.auth_token}"},
|
headers={"Authorization": f"Bearer {self.auth_token}"},
|
||||||
)
|
)
|
||||||
|
|
||||||
data = response.json()
|
available_resources = deduplicate_available_resources(response.json())
|
||||||
|
|
||||||
if len(data) == 0:
|
if len(available_resources) == 0:
|
||||||
raise JiraToolExecutionError(
|
raise JiraToolExecutionError(
|
||||||
message="No cloud ID returned by Atlassian, cannot make API calls"
|
message="No cloud ID returned by Atlassian, cannot make API calls"
|
||||||
)
|
)
|
||||||
if len(data) > 1:
|
if len(available_resources) > 1:
|
||||||
cloud_ids_found = json.dumps([
|
cloud_ids_found = json.dumps([
|
||||||
{
|
{
|
||||||
"id": item["id"],
|
"id": resource["id"],
|
||||||
"name": item["name"],
|
"name": resource["name"],
|
||||||
"url": item["url"],
|
"url": resource["url"],
|
||||||
}
|
}
|
||||||
for item in data
|
for resource in available_resources
|
||||||
])
|
])
|
||||||
raise JiraToolExecutionError(
|
raise JiraToolExecutionError(
|
||||||
message=f"Multiple cloud IDs returned by Atlassian: {cloud_ids_found}. "
|
message=(
|
||||||
"Cannot resolve which one to use."
|
"Multiple cloud IDs returned by Atlassian, cannot resolve which one "
|
||||||
|
"to use. Please revoke your authorization access and authorize a single "
|
||||||
|
f"Atlassian Cloud. Available cloud IDs: {cloud_ids_found}. "
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return cast(dict[str, Any], data[0])
|
return cast(dict[str, Any], available_resources[0])
|
||||||
|
|
||||||
def _build_error_messages(self, response: httpx.Response) -> tuple[str, str | None]:
|
def _build_error_messages(self, response: httpx.Response) -> tuple[str, str | None]:
|
||||||
try:
|
try:
|
||||||
|
|
@ -224,3 +227,15 @@ class JiraClient:
|
||||||
self._raise_for_status(response)
|
self._raise_for_status(response)
|
||||||
|
|
||||||
return self._format_response_dict(response)
|
return self._format_response_dict(response)
|
||||||
|
|
||||||
|
|
||||||
|
def deduplicate_available_resources(available_resources: list[dict]) -> list[dict]:
|
||||||
|
account_ids_seen = set()
|
||||||
|
deduplicated = []
|
||||||
|
|
||||||
|
for item in available_resources:
|
||||||
|
if item["id"] not in account_ids_seen:
|
||||||
|
deduplicated.append(item)
|
||||||
|
account_ids_seen.add(item["id"])
|
||||||
|
|
||||||
|
return deduplicated
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_jira"
|
name = "arcade_jira"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "Arcade.dev LLM tools for interacting with Atlassian Jira"
|
description = "Arcade.dev LLM tools for interacting with Atlassian Jira"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
||||||
63
toolkits/jira/tests/test_client.py
Normal file
63
toolkits/jira/tests/test_client.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from arcade_jira.client import JiraClient
|
||||||
|
from arcade_jira.exceptions import JiraToolExecutionError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_cloud_data_from_available_resources_single_cloud(
|
||||||
|
mock_httpx_client, fake_auth_token
|
||||||
|
):
|
||||||
|
cloud = {"id": "123", "name": "Test Cloud", "url": "https://test.atlassian.net"}
|
||||||
|
|
||||||
|
client = JiraClient(auth_token=fake_auth_token)
|
||||||
|
|
||||||
|
mock_httpx_client.get.return_value = httpx.Response(
|
||||||
|
status_code=200,
|
||||||
|
json=[cloud],
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client._get_cloud_data_from_available_resources()
|
||||||
|
assert response == cloud
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_cloud_data_from_available_resources_multiple_clouds(
|
||||||
|
mock_httpx_client, fake_auth_token
|
||||||
|
):
|
||||||
|
cloud1 = {"id": "123", "name": "Test Cloud", "url": "https://test.atlassian.net"}
|
||||||
|
cloud2 = {"id": "456", "name": "Test Cloud 2", "url": "https://test2.atlassian.net"}
|
||||||
|
|
||||||
|
client = JiraClient(auth_token=fake_auth_token)
|
||||||
|
|
||||||
|
mock_httpx_client.get.return_value = httpx.Response(
|
||||||
|
status_code=200,
|
||||||
|
json=[cloud1, cloud2],
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(JiraToolExecutionError) as error:
|
||||||
|
await client._get_cloud_data_from_available_resources()
|
||||||
|
|
||||||
|
assert "Multiple cloud IDs returned by Atlassian" in error.value.message
|
||||||
|
assert json.dumps(cloud1) in error.value.message
|
||||||
|
assert json.dumps(cloud2) in error.value.message
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_cloud_data_from_available_resources_duplicate_cloud(
|
||||||
|
mock_httpx_client, fake_auth_token
|
||||||
|
):
|
||||||
|
cloud = {"id": "123", "name": "Test Cloud", "url": "https://test.atlassian.net"}
|
||||||
|
|
||||||
|
client = JiraClient(auth_token=fake_auth_token)
|
||||||
|
|
||||||
|
mock_httpx_client.get.return_value = httpx.Response(
|
||||||
|
status_code=200,
|
||||||
|
json=[cloud, cloud],
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client._get_cloud_data_from_available_resources()
|
||||||
|
assert response == cloud
|
||||||
Loading…
Reference in a new issue