Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(sentry apps): Improve Sentry App Errors #81877

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
6 changes: 4 additions & 2 deletions src/sentry/api/endpoints/project_rule_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
from sentry.integrations.slack.utils.rule_status import RedisRuleStatus
from sentry.models.rule import NeglectedRule, RuleActivity, RuleActivityType
from sentry.projects.project_rules.updater import ProjectRuleUpdater
from sentry.rules.actions import trigger_sentry_app_action_creators_for_issues
from sentry.rules.actions.utils import get_changed_data, get_updated_rule_data
from sentry.sentry_apps.utils.alert_rule_action import create_sentry_app_alert_rule_issues_component
from sentry.signals import alert_rule_edited
from sentry.types.actor import Actor
from sentry.utils import metrics
Expand Down Expand Up @@ -285,7 +285,9 @@ def put(self, request: Request, project, rule) -> Response:
context = {"uuid": client.uuid}
return Response(context, status=202)

trigger_sentry_app_action_creators_for_issues(actions=kwargs["actions"])
result = create_sentry_app_alert_rule_issues_component(actions=kwargs["actions"])
if isinstance(result, Response):
return result

updated_rule = ProjectRuleUpdater(
rule=rule,
Expand Down
9 changes: 6 additions & 3 deletions src/sentry/api/endpoints/project_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
from sentry.integrations.slack.utils.rule_status import RedisRuleStatus
from sentry.models.rule import Rule, RuleActivity, RuleActivityType
from sentry.projects.project_rules.creator import ProjectRuleCreator
from sentry.rules.actions import trigger_sentry_app_action_creators_for_issues
from sentry.rules.actions.base import instantiate_action
from sentry.rules.processing.processor import is_condition_slow
from sentry.sentry_apps.utils.alert_rule_action import create_sentry_app_alert_rule_issues_component
from sentry.signals import alert_rule_created
from sentry.utils import metrics

Expand Down Expand Up @@ -841,9 +841,12 @@ def post(self, request: Request, project) -> Response:
find_channel_id_for_rule.apply_async(kwargs=kwargs)
return Response(uuid_context, status=202)

created_alert_rule_ui_component = trigger_sentry_app_action_creators_for_issues(
kwargs["actions"]
created_alert_rule_ui_component = create_sentry_app_alert_rule_issues_component(
actions=kwargs["actions"]
)
if isinstance(created_alert_rule_ui_component, Response):
return created_alert_rule_ui_component

rule = ProjectRuleCreator(
name=kwargs["name"],
project=project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
get_slack_actions_with_async_lookups,
)
from sentry.incidents.serializers import AlertRuleSerializer as DrfAlertRuleSerializer
from sentry.incidents.utils.sentry_apps import trigger_sentry_app_action_creators_for_incidents
from sentry.integrations.slack.tasks.find_channel_id_for_alert_rule import (
find_channel_id_for_alert_rule,
)
from sentry.integrations.slack.utils.rule_status import RedisRuleStatus
from sentry.models.rulesnooze import RuleSnooze
from sentry.sentry_apps.services.app import app_service
from sentry.sentry_apps.utils.alert_rule_action import (
create_sentry_app_alert_rule_component_for_incidents,
)
from sentry.users.services.user.service import user_service


Expand Down Expand Up @@ -82,7 +84,12 @@ def update_alert_rule(request: Request, organization, alert_rule):
partial=True,
)
if serializer.is_valid():
trigger_sentry_app_action_creators_for_incidents(serializer.validated_data)
raised_error = create_sentry_app_alert_rule_component_for_incidents(
serializer.validated_data
)
if raised_error:
return raised_error

if get_slack_actions_with_async_lookups(organization, request.user, data):
# need to kick off an async job for Slack
client = RedisRuleStatus()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
from sentry.incidents.models.alert_rule import AlertRule
from sentry.incidents.models.incident import Incident, IncidentStatus
from sentry.incidents.serializers import AlertRuleSerializer as DrfAlertRuleSerializer
from sentry.incidents.utils.sentry_apps import trigger_sentry_app_action_creators_for_incidents
from sentry.integrations.slack.tasks.find_channel_id_for_alert_rule import (
find_channel_id_for_alert_rule,
)
Expand All @@ -52,6 +51,9 @@
from sentry.models.rule import Rule, RuleSource
from sentry.models.team import Team
from sentry.sentry_apps.services.app import app_service
from sentry.sentry_apps.utils.alert_rule_action import (
create_sentry_app_alert_rule_component_for_incidents,
)
from sentry.snuba.dataset import Dataset
from sentry.snuba.models import SnubaQuery
from sentry.uptime.models import (
Expand Down Expand Up @@ -120,7 +122,12 @@ def create_metric_alert(
if not serializer.is_valid():
raise ValidationError(serializer.errors)

trigger_sentry_app_action_creators_for_incidents(serializer.validated_data)
raised_error = create_sentry_app_alert_rule_component_for_incidents(
serializer.validated_data
)
if raised_error:
return raised_error

if get_slack_actions_with_async_lookups(organization, request.user, request.data):
# need to kick off an async job for Slack
client = RedisRuleStatus()
Expand Down
36 changes: 0 additions & 36 deletions src/sentry/incidents/utils/sentry_apps.py

This file was deleted.

2 changes: 0 additions & 2 deletions src/sentry/rules/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
IntegrationNotifyServiceForm,
TicketEventAction,
)
from sentry.rules.actions.sentry_apps import trigger_sentry_app_action_creators_for_issues

__all__ = (
"EventAction",
"IntegrationEventAction",
"IntegrationNotifyServiceForm",
"TicketEventAction",
"trigger_sentry_app_action_creators_for_issues",
)
2 changes: 0 additions & 2 deletions src/sentry/rules/actions/sentry_apps/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from .base import SentryAppEventAction
from .notify_event import NotifyEventSentryAppAction
from .utils import trigger_sentry_app_action_creators_for_issues

__all__ = (
"NotifyEventSentryAppAction",
"SentryAppEventAction",
"trigger_sentry_app_action_creators_for_issues",
)
28 changes: 0 additions & 28 deletions src/sentry/rules/actions/sentry_apps/utils.py

This file was deleted.

15 changes: 9 additions & 6 deletions src/sentry/sentry_apps/alert_rule_action_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
AlertRuleActionRequester,
AlertRuleActionResult,
)
from sentry.sentry_apps.models.sentry_app import SentryApp
from sentry.sentry_apps.models.sentry_app_component import SentryAppComponent
from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation
from sentry.sentry_apps.utils.errors import SentryAppIntegratorError


@dataclass
Expand All @@ -25,16 +27,17 @@ def run(self) -> AlertRuleActionResult:
response = self._make_external_request(uri)
return response

def _fetch_sentry_app_uri(self):
def _fetch_sentry_app_uri(self) -> str:
component = SentryAppComponent.objects.get(
type="alert-rule-action", sentry_app=self.sentry_app
)
settings = component.schema.get("settings", {})
return settings.get("uri")
uri = settings.get("uri", None)
if not uri:
raise SentryAppIntegratorError(APIError("Sentry App request url not found on schema"))
return uri

def _make_external_request(self, uri=None):
if uri is None:
raise APIError("Sentry App request url not found")
def _make_external_request(self, uri: str) -> AlertRuleActionResult:
response = AlertRuleActionRequester(
install=self.install,
uri=uri,
Expand All @@ -44,5 +47,5 @@ def _make_external_request(self, uri=None):
return response

@cached_property
def sentry_app(self):
def sentry_app(self) -> SentryApp:
return self.install.sentry_app
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from django.utils.functional import empty
from jsonschema import ValidationError
from rest_framework.request import Request
from rest_framework.response import Response
from sentry_sdk import capture_exception
Expand All @@ -8,14 +7,14 @@
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.serializers import serialize
from sentry.coreapi import APIError, APIUnauthorized
from sentry.models.group import Group
from sentry.models.project import Project
from sentry.sentry_apps.api.bases.sentryapps import SentryAppInstallationBaseEndpoint
from sentry.sentry_apps.api.serializers.platform_external_issue import (
PlatformExternalIssueSerializer,
)
from sentry.sentry_apps.external_issues.issue_link_creator import IssueLinkCreator
from sentry.sentry_apps.utils.errors import SentryAppError, SentryAppIntegratorError
from sentry.users.models.user import User
from sentry.users.services.user.serial import serialize_rpc_user

Expand Down Expand Up @@ -74,9 +73,9 @@
uri=uri,
user=user,
).run()
except (APIError, ValidationError, APIUnauthorized) as e:
except (SentryAppError, SentryAppIntegratorError) as e:
return Response({"error": str(e)}, status=400)
except Exception as e:

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
error_id = capture_exception(e)
return Response(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def delete(self, request: Request, installation, external_issue_id) -> Response:
service_type=installation.sentry_app.slug,
)
except PlatformExternalIssue.DoesNotExist:
return Response(status=404)
return Response({"error": "Could not find requested external issue"}, status=404)

deletions.exec_sync(platform_external_issue)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import logging

from jsonschema import ValidationError
import sentry_sdk
from rest_framework.request import Request
from rest_framework.response import Response

from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.coreapi import APIError
from sentry.models.project import Project
from sentry.sentry_apps.api.bases.sentryapps import SentryAppInstallationBaseEndpoint
from sentry.sentry_apps.external_requests.select_requester import SelectRequester
from sentry.sentry_apps.utils.errors import SentryAppError, SentryAppIntegratorError

logger = logging.getLogger("sentry.sentry-apps")

Expand Down Expand Up @@ -42,16 +42,18 @@

try:
choices = SelectRequester(**kwargs).run()
except ValidationError as e:
except (SentryAppIntegratorError, SentryAppError) as e:
return Response(
{"error": e.message},
{"error": str(e)},

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
Stack trace information
flows to this location and may be exposed to an external user.
Stack trace information
flows to this location and may be exposed to an external user.
Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix AI 28 days ago

To fix the problem, we should ensure that detailed error information is logged on the server while returning a generic error message to the user. This can be achieved by logging the exception details and returning a user-friendly error message in the response.

  • Modify the code to log the exception details using the logger and return a generic error message to the user.
  • Ensure that the logging captures sufficient information for debugging purposes without exposing sensitive details to the end user.
Suggested changeset 1
src/sentry/sentry_apps/api/endpoints/installation_external_requests.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/sentry/sentry_apps/api/endpoints/installation_external_requests.py b/src/sentry/sentry_apps/api/endpoints/installation_external_requests.py
--- a/src/sentry/sentry_apps/api/endpoints/installation_external_requests.py
+++ b/src/sentry/sentry_apps/api/endpoints/installation_external_requests.py
@@ -45,4 +45,5 @@
         except (SentryAppIntegratorError, SentryAppError) as e:
+            logger.error("Error occurred while getting Select FormField options: %s", str(e))
             return Response(
-                {"error": str(e)},
+                {"error": "An error occurred while processing your request."},
                 status=400,
EOF
@@ -45,4 +45,5 @@
except (SentryAppIntegratorError, SentryAppError) as e:
logger.error("Error occurred while getting Select FormField options: %s", str(e))
return Response(
{"error": str(e)},
{"error": "An error occurred while processing your request."},
status=400,
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
status=400,
)
except APIError:
message = f'Error retrieving select field options from {request.GET.get("uri")}'
except Exception as e:
error_id = sentry_sdk.capture_exception(e)
return Response(
{"error": message},
status=400,
{
"error": f"Something went wrong while trying to get Select FormField options. Sentry error ID: {error_id}"
},
status=500,
)

return Response(choices)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sentry.sentry_apps.external_requests.issue_link_requester import IssueLinkRequester
from sentry.sentry_apps.models.platformexternalissue import PlatformExternalIssue
from sentry.sentry_apps.services.app import RpcSentryAppInstallation
from sentry.sentry_apps.utils.errors import SentryAppError
from sentry.users.services.user import RpcUser

VALID_ACTIONS = ["link", "create"]
Expand All @@ -32,7 +33,7 @@ def run(self) -> PlatformExternalIssue:

def _verify_action(self) -> None:
if self.action not in VALID_ACTIONS:
raise APIUnauthorized(f"Invalid action '{self.action}'")
raise SentryAppError(APIUnauthorized(f"Invalid action '{self.action}'"))

def _make_external_request(self) -> dict[str, Any]:
response = IssueLinkRequester(
Expand Down
Loading
Loading