Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
from typing import Any

from pydantic import BaseModel
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request
Expand Down Expand Up @@ -108,6 +109,11 @@
"""


class DashboardGenerateResponse(BaseModel):
run_id: int
sentry_run_id: str


class DashboardGenerateSerializer(serializers.Serializer[dict[str, Any]]):
prompt = serializers.CharField(
required=True,
Expand Down Expand Up @@ -198,7 +204,11 @@ def post(self, request: Request, organization: Organization) -> Response:
artifact_schema=GeneratedDashboard,
request=request,
)
return Response({"run_id": run.seer_run_state_id})
return Response(
DashboardGenerateResponse(
run_id=run.seer_run_state_id, sentry_run_id=str(run.uuid)
).dict()
)
except SeerPermissionError as e:
raise PermissionDenied(e.message) from e
except SeerApiError:
Expand Down
22 changes: 21 additions & 1 deletion src/sentry/seer/endpoints/organization_seer_agent_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
from sentry.api.paginator import GenericOffsetPaginator
from sentry.models.organization import Organization
from sentry.seer.agent.client import SeerAgentClient
from sentry.seer.agent.client_models import AgentRun
from sentry.seer.agent.client_utils import has_seer_agent_access_with_detail
from sentry.seer.models import SeerPermissionError
from sentry.seer.models.run import SeerRun

logger = logging.getLogger(__name__)


class AgentRunWithUuid(AgentRun):
sentry_run_id: str | None


class OrganizationSeerAgentRunsPermission(OrganizationPermission):
scope_map = {
"GET": ["org:read"],
Expand Down Expand Up @@ -72,7 +78,21 @@ def _make_seer_runs_request(offset: int, limit: int) -> dict[str, Any]:
except SeerPermissionError as e:
raise PermissionDenied(e.message) from e

return {"data": [run.dict() for run in runs]}
uuid_by_state_id = {
seer_run_state_id: str(run_uuid)
for seer_run_state_id, run_uuid in SeerRun.objects.filter(
organization=organization,
seer_run_state_id__in=[run.run_id for run in runs],
).values_list("seer_run_state_id", "uuid")
}
return {
"data": [
AgentRunWithUuid(
**run.dict(), sentry_run_id=uuid_by_state_id.get(run.run_id)
).dict()
for run in runs
]
}

return self.paginate(
request=request,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import uuid
from typing import Any
from unittest.mock import ANY, MagicMock, patch

Expand Down Expand Up @@ -31,15 +32,16 @@ def test_post_with_empty_prompt_returns_400(self) -> None:

@patch("sentry.dashboards.endpoints.organization_dashboard_generate.SeerAgentClient")
def test_post_starts_run_and_returns_run_id(self, mock_client_class: MagicMock) -> None:
run_uuid = uuid.uuid4()
mock_client = MagicMock()
mock_client.start_run.return_value = MagicMock(seer_run_state_id=789)
mock_client.start_run.return_value = MagicMock(seer_run_state_id=789, uuid=run_uuid)
mock_client_class.return_value = mock_client

data = {"prompt": "Show me error rates by project"}
response = self.client.post(self.url, data, format="json")

assert response.status_code == 200
assert response.data == {"run_id": 789}
assert response.data == {"run_id": 789, "sentry_run_id": str(run_uuid)}

mock_client_class.assert_called_once_with(
self.organization,
Expand Down Expand Up @@ -104,8 +106,9 @@ def test_post_with_invalid_current_dashboard_returns_400(self) -> None:
def test_post_with_current_dashboard_uses_edit_context(
self, mock_client_class: MagicMock
) -> None:
run_uuid = uuid.uuid4()
mock_client = MagicMock()
mock_client.start_run.return_value = MagicMock(seer_run_state_id=123)
mock_client.start_run.return_value = MagicMock(seer_run_state_id=123, uuid=run_uuid)
mock_client_class.return_value = mock_client

data = {
Expand Down Expand Up @@ -137,7 +140,7 @@ def test_post_with_current_dashboard_uses_edit_context(
response = self.client.post(self.url, data, format="json")

assert response.status_code == 200
assert response.data == {"run_id": 123}
assert response.data == {"run_id": 123, "sentry_run_id": str(run_uuid)}

mock_client_class.assert_called_once_with(
self.organization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,28 @@ def test_get_simple(self) -> None:
query=None,
)

def test_get_includes_sentry_run_id(self) -> None:
run = self.create_seer_run(organization=self.organization, seer_run_state_id=1)
self.mock_client.get_runs.return_value = [
AgentRun(
run_id=1,
title="Mirrored",
last_triggered_at=datetime.now(),
created_at=datetime.now(),
),
AgentRun(
run_id=2,
title="Legacy (no mirror)",
last_triggered_at=datetime.now(),
created_at=datetime.now(),
),
]
response = self.client.get(self.url)
assert response.status_code == 200
data = response.json()["data"]
assert data[0]["sentry_run_id"] == str(run.uuid)
assert data[1]["sentry_run_id"] is None

def test_get_cursor_pagination(self) -> None:
# Mock seer response for offset 0, limit 3.
self.mock_client.get_runs.return_value = [
Expand Down
Loading