diff --git a/src/sentry/sentry_apps/external_requests/select_requester.py b/src/sentry/sentry_apps/external_requests/select_requester.py index b7e746811f3e7..9df1240653f15 100644 --- a/src/sentry/sentry_apps/external_requests/select_requester.py +++ b/src/sentry/sentry_apps/external_requests/select_requester.py @@ -1,6 +1,7 @@ import logging +from collections.abc import Sequence from dataclasses import dataclass, field -from typing import Any +from typing import Annotated, Any, TypedDict from urllib.parse import urlencode, urlparse, urlunparse from uuid import uuid4 @@ -17,6 +18,12 @@ logger = logging.getLogger("sentry.sentry_apps.external_requests") +class SelectRequesterResult(TypedDict, total=False): + # Each contained Sequence of strings is of length 2 i.e ["label", "value"] + choices: Sequence[Annotated[Sequence[str], 2]] + defaultValue: str + + @dataclass class SelectRequester: """ @@ -34,7 +41,7 @@ class SelectRequester: query: str | None = field(default=None) dependent_data: str | None = field(default=None) - def run(self) -> dict[str, Any]: + def run(self) -> SelectRequesterResult: response: list[dict[str, str]] = [] try: url = self._build_url() @@ -71,7 +78,9 @@ def run(self) -> dict[str, Any]: message = "select-requester.request-failed" logger.info(message, extra=extra) - raise APIError from e + raise APIError( + f"Something went wrong while getting SelectFields from {self.sentry_app.slug}" + ) from e if not self._validate_response(response): logger.info( @@ -85,7 +94,7 @@ def run(self) -> dict[str, Any]: }, ) raise ValidationError( - f"Invalid response format for SelectField in {self.sentry_app} from uri: {self.uri}" + f"Invalid response format for SelectField in {self.sentry_app.slug} from uri: {self.uri}" ) return self._format_response(response) @@ -107,14 +116,16 @@ def _build_url(self) -> str: urlparts[4] = urlencode(query) return str(urlunparse(urlparts)) - def _validate_response(self, resp: list[dict[str, Any]]) -> bool: + # response format must be: + # https://docs.sentry.io/organization/integrations/integration-platform/ui-components/formfield/#uri-response-format + def _validate_response(self, resp: Sequence[dict[str, Any]]) -> bool: return validate(instance=resp, schema_type="select") - def _format_response(self, resp: list[dict[str, Any]]) -> dict[str, Any]: + def _format_response(self, resp: Sequence[dict[str, Any]]) -> SelectRequesterResult: # the UI expects the following form: # choices: [[label, value]] # default: [label, value] - response: dict[str, Any] = {} + response: SelectRequesterResult = {} choices: list[list[str]] = [] for option in resp: diff --git a/tests/sentry/sentry_apps/external_requests/test_select_requester.py b/tests/sentry/sentry_apps/external_requests/test_select_requester.py index 63a726515e5b5..45ad13df13145 100644 --- a/tests/sentry/sentry_apps/external_requests/test_select_requester.py +++ b/tests/sentry/sentry_apps/external_requests/test_select_requester.py @@ -120,3 +120,66 @@ def test_500_response(self): assert len(requests) == 1 assert requests[0]["response_code"] == 500 assert requests[0]["event_type"] == "select_options.requested" + + @responses.activate + def test_api_error_message(self): + responses.add( + method=responses.GET, + url=f"https://example.com/get-issues?installationId={self.install.uuid}&projectSlug={self.project.slug}", + body="Something failed", + status=500, + ) + + with pytest.raises(APIError) as exception_info: + SelectRequester( + install=self.install, + project_slug=self.project.slug, + uri="/get-issues", + ).run() + assert ( + str(exception_info.value) + == f"Something went wrong while getting SelectFields from {self.sentry_app.slug}" + ) + + @responses.activate + def test_validation_error_message_validator(self): + uri = "/get-issues" + + responses.add( + method=responses.GET, + url=f"https://example.com/get-issues?installationId={self.install.uuid}&projectSlug={self.project.slug}", + json={}, + status=200, + ) + + with pytest.raises(ValidationError) as exception_info: + SelectRequester( + install=self.install, + project_slug=self.project.slug, + uri=uri, + ).run() + + assert ( + str(exception_info.value) + == f"Invalid response format for SelectField in {self.sentry_app.slug} from uri: {uri}" + ) + + @responses.activate + def test_validation_error_message_missing_field(self): + responses.add( + method=responses.GET, + url=f"https://example.com/get-issues?installationId={self.install.uuid}&projectSlug={self.project.slug}", + json=[{"bruh": "bruhhhhh"}], + status=200, + ) + + with pytest.raises(ValidationError) as exception_info: + SelectRequester( + install=self.install, + project_slug=self.project.slug, + uri="/get-issues", + ).run() + + assert ( + str(exception_info.value) == "Missing `value` or `label` in option data for SelectField" + )