-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
feat(related_issues): API to find groups caused by the same root error #66992
Changes from all commits
7c22665
2a3ed0a
9a1fba5
ac7b35a
8ea8539
6ad3dbb
39f28cc
9fe38a3
582ef11
7a50235
f160cfe
5354a19
8fee0e2
c97f46f
ca63bf6
74edddb
7d7b75b
d1a03d1
2107598
3ed0265
fa06db7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -480,24 +480,24 @@ static/app/components/events/eventStatisticalDetector/ @getse | |
|
||
|
||
## Issues | ||
**/issues/** @getsentry/issues | ||
/src/sentry/api/helpers/source_map_helper.py @getsentry/issues | ||
/src/sentry/api/helpers/actionable_items_helper.py @getsentry/issues | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless I make an explicit comment, I'm just re-ordering the list alphabetically. |
||
/src/sentry/event_manager.py @getsentry/issues | ||
/src/sentry/grouping/ @getsentry/issues | ||
/src/sentry/issues/ @getsentry/issues | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing. It is now matched by the global matcher at the top. |
||
/src/sentry/tasks/post_process.py @getsentry/issues | ||
/src/sentry/tasks/unmerge.py @getsentry/issues | ||
/src/sentry/search/snuba/ @getsentry/issues | ||
/tests/sentry/event_manager/ @getsentry/issues | ||
/tests/sentry/grouping/ @getsentry/issues | ||
/tests/sentry/issues/ @getsentry/issues | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing. It is now matched by the global matcher at the top. |
||
/tests/sentry/search/ @getsentry/issues | ||
/tests/sentry/tasks/test_post_process.py @getsentry/issues | ||
/tests/snuba/search/ @getsentry/issues | ||
/src/sentry/search/snuba/ @getsentry/issues | ||
/static/app/views/issueList @getsentry/issues | ||
/src/sentry/api/helpers/source_map_helper.py @getsentry/issues | ||
/src/sentry/api/helpers/actionable_items_helper.py @getsentry/issues | ||
/static/app/views/issueList @getsentry/issues | ||
/static/app/views/issueList @getsentry/issues | ||
/static/app/components/events/interfaces/crashContent/exception/actionableItems.tsx @getsentry/issues | ||
/static/app/utils/analytics.tsx @getsentry/issues | ||
/static/app/utils/routeAnalytics/ @getsentry/issues | ||
/static/app/utils/analytics.tsx @getsentry/issues | ||
/static/app/utils/routeAnalytics/ @getsentry/issues | ||
## End of Issues | ||
|
||
## Billing | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
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.api.bases.group import GroupEndpoint | ||
from sentry.issues.related import find_related_issues | ||
from sentry.models.group import Group | ||
|
||
|
||
@region_silo_endpoint | ||
class RelatedIssuesEndpoint(GroupEndpoint): | ||
owner = ApiOwner.ISSUES | ||
publish_status = {"GET": ApiPublishStatus.EXPERIMENTAL} | ||
|
||
def get(self, _: Request, group: Group) -> Response: | ||
related_issues = find_related_issues(group) | ||
return Response({key: [g.id for g in groups] for key, groups in related_issues.items()}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Summary | ||
|
||
Issues in Sentry are created based on unique fingerprints based on the information from an event (e.g. the stack trace or the message). The Related Issues feature associates different issues based on the heuristics we will describe. This satisfies the desire of many customers to act on various issues together. | ||
|
||
In the future, we will be implementing super groups ([read Armin's RFC on supergroups](https://github.com/getsentry/rfcs/pull/29)). | ||
|
||
## Same root cause | ||
|
||
In many cases, a bug or an environmental failure will create many Sentry issues which can be merged. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"""This module exports a function to find related issues. It groups them by type.""" | ||
|
||
from sentry.models.group import Group | ||
|
||
from .same_root_cause import same_root_cause_analysis | ||
|
||
__all__ = ["find_related_issues"] | ||
|
||
RELATED_ISSUES_ALGORITHMS = { | ||
"same_root_cause": same_root_cause_analysis, | ||
} | ||
|
||
|
||
def find_related_issues(group: Group) -> dict[str, list[Group]]: | ||
related_issues = {} | ||
for key, func in RELATED_ISSUES_ALGORITHMS.items(): | ||
related_issues[key] = func(group) | ||
|
||
return related_issues or {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Module to evaluate if groups have the same root cause | ||
# | ||
# The first case this module handles is environmental failures. | ||
# | ||
# Refer to README in module for more details. | ||
from typing import Any | ||
|
||
from sentry.models.group import Group | ||
from sentry.utils.query import RangeQuerySetWrapper | ||
|
||
|
||
def same_root_cause_analysis(group: Group) -> list[Group]: | ||
"""Analyze and create a group set if the group was caused by the same root cause.""" | ||
# Querying the data field (which is a GzippedDictField) cannot be done via | ||
# Django's ORM, thus, we do so via compare_groups | ||
project_groups = RangeQuerySetWrapper(Group.objects.filter(project=group.project_id), limit=100) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've addressed Dan's concern and started using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're limiting to 100 rows total you don't need the ranged wrapper, the limit is enough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay. This is not a permanent change. I will tweak it later. |
||
same_error_type_groups = [g for g in project_groups if compare_groups(g, group)] | ||
return same_error_type_groups or [] | ||
|
||
|
||
def compare_groups(groupA: Group, groupB: Group) -> bool: | ||
return match_criteria(_extract_values(groupA), _extract_values(groupB)) | ||
|
||
|
||
def match_criteria(a: dict[str, str | None], b: dict[str, str | None]) -> bool: | ||
# XXX: In future iterations we will be able to use similar titles rather than an exact match | ||
return a["type"] == b["type"] and a["title"] == b["title"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very simplistic but it works in a lot of cases. |
||
|
||
|
||
def _extract_values(group: Group) -> dict[str, Any]: | ||
return {"title": group.title, "type": group.data.get("metadata", {}).get("type")} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from typing import Any | ||
|
||
from django.urls import reverse | ||
|
||
from sentry.testutils.cases import APITestCase | ||
from sentry.testutils.silo import region_silo_test | ||
|
||
|
||
@region_silo_test | ||
class RelatedIssuesTest(APITestCase): | ||
endpoint = "sentry-api-0-issues-related-issues" | ||
|
||
def setUp(self) -> None: | ||
super().setUp() | ||
self.login_as(user=self.user) | ||
self.organization = self.create_organization(owner=self.user) | ||
self.error_type = "ApiTimeoutError" | ||
self.error_value = "Timed out attempting to reach host: api.github.com" | ||
# You need to set this value in your test before calling the API | ||
self.group_id = None | ||
|
||
def reverse_url(self) -> str: | ||
return reverse(self.endpoint, kwargs={"issue_id": self.group_id}) | ||
|
||
def _data(self, type: str, value: str) -> dict[str, Any]: | ||
return {"type": "error", "metadata": {"type": type, "value": value}} | ||
|
||
def test_same_root_related_issues(self) -> None: | ||
# This is the group we're going to query about | ||
group = self.create_group(data=self._data(self.error_type, self.error_value)) | ||
self.group_id = group.id | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will allow |
||
|
||
groups_data = [ | ||
self._data("ApiError", self.error_value), | ||
self._data(self.error_type, "Unreacheable host: api.github.com"), | ||
self._data(self.error_type, ""), | ||
# Only this group will be related | ||
self._data(self.error_type, self.error_value), | ||
] | ||
# XXX: See if we can get this code to be closer to how save_event generates groups | ||
for datum in groups_data: | ||
self.create_group(data=datum) | ||
|
||
response = self.get_success_response() | ||
# The UI will then make normal calls to get issues-stats | ||
# For instance, this URL | ||
# https://us.sentry.io/api/0/organizations/sentry/issues-stats/?groups=4741828952&groups=4489703641&statsPeriod=24h | ||
assert response.json() == {"same_root_cause": [1, 5]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This matches all paths that contain the issues directory. I've tested it.