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
30 changes: 15 additions & 15 deletions src/sentry/api/helpers/group_index/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,27 +176,27 @@ def validate_search_filter_permissions(
)


def get_by_short_id(
def get_by_short_ids(
organization_id: int,
is_short_id_lookup: str,
query: str,
*,
project_ids: Collection[int] | None,
) -> Group | None:
) -> list[Group]:
# Match short id tokens anywhere in the query
if is_short_id_lookup != "1":
return None
# A short id token anywhere in the query is treated as a direct hit,
# so it composes with filters. project_ids scopes the lookup so a short id for a project
# the caller cannot access does not resolve; pass None only for org-wide callers.
for token in query.split():
if looks_like_short_id(token):
try:
return Group.objects.by_qualified_short_id(
organization_id, token, project_ids=project_ids
)
except Group.DoesNotExist:
continue
return None
return []
groups: list[Group] = []
for token in set(query.split()):
if not looks_like_short_id(token):
continue
try:
groups.append(
Group.objects.by_qualified_short_id(organization_id, token, project_ids=project_ids)
)
except Group.DoesNotExist:
continue
return groups


def track_slo_response(name: str) -> Callable[[EndpointFunction], EndpointFunction]:
Expand Down
22 changes: 12 additions & 10 deletions src/sentry/issues/endpoints/organization_group_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from sentry.api.helpers.group_index import (
build_query_params_from_request,
calculate_stats_period,
get_by_short_id,
get_by_short_ids,
schedule_tasks_to_delete_groups,
track_slo_response,
update_groups_with_search_fn,
Expand Down Expand Up @@ -406,21 +406,23 @@ def get(
)
return Response(by_event)

group = get_by_short_id(
short_id_groups = get_by_short_ids(
organization.id,
request.GET.get("shortIdLookup") or "0",
query,
project_ids=None,
)
if group is not None:
# check all projects user has access to
if request.access.has_project_access(group.project):
by_short_id: list[StreamGroupSerializerSnubaResponse] = serialize(
[group], request.user, serializer(), request=request
)
response = Response(by_short_id)
accessible = [
g for g in short_id_groups if request.access.has_project_access(g.project)
]
if accessible:
by_short_id: list[StreamGroupSerializerSnubaResponse] = serialize(
accessible, request.user, serializer(), request=request
)
response = Response(by_short_id)
if len(accessible) == 1:
response["X-Sentry-Direct-Hit"] = "1"
return response
return response

# If group ids specified, just ignore any query components
try:
Expand Down
22 changes: 10 additions & 12 deletions src/sentry/issues/endpoints/project_group_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from sentry.api.bases.project import ProjectEndpoint, ProjectEventPermission
from sentry.api.helpers.environments import get_environment_func
from sentry.api.helpers.group_index import (
get_by_short_id,
get_by_short_ids,
prep_search,
schedule_tasks_to_delete_groups,
track_slo_response,
Expand Down Expand Up @@ -162,26 +162,26 @@ def get(self, request: Request, project: Project) -> Response:
return Response(serialized_groups)

if query:
matching_group = None
matching_groups: list[Group] = []
matching_event = None
event_id = normalize_event_id(query)
if event_id:
# check to see if we've got an event ID
try:
matching_group = Group.objects.from_event_id(project, event_id)
matching_groups = [Group.objects.from_event_id(project, event_id)]
except Group.DoesNotExist:
pass
else:
matching_event = eventstore.backend.get_event_by_id(project.id, event_id)
elif matching_group is None:
matching_group = get_by_short_id(
else:
matching_groups = get_by_short_ids(
project.organization_id,
request.GET.get("shortIdLookup", "0"),
query,
project_ids=[project.id],
)

if matching_group is not None:
if matching_groups:
matching_event_environment = None

try:
Expand All @@ -191,18 +191,16 @@ def get(self, request: Request, project: Project) -> Response:
except Environment.DoesNotExist:
pass

serialized_groups = serialize([matching_group], request.user, serializer)
serialized_groups = serialize(matching_groups, request.user, serializer)
matching_event_id = getattr(matching_event, "event_id", None)
if matching_event_id:
serialized_groups[0]["matchingEventId"] = getattr(
matching_event, "event_id", None
)
serialized_groups[0]["matchingEventId"] = matching_event_id
if matching_event_environment:
serialized_groups[0]["matchingEventEnvironment"] = matching_event_environment

response = Response(serialized_groups)

response["X-Sentry-Direct-Hit"] = "1"
if len(matching_groups) == 1:
response["X-Sentry-Direct-Hit"] = "1"
return response

try:
Expand Down
11 changes: 11 additions & 0 deletions tests/sentry/issues/endpoints/test_organization_group_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,17 @@ def test_lookup_by_short_id_with_filter_resolved(self) -> None:
assert response.data[0]["id"] == str(group.id)
assert response["X-Sentry-Direct-Hit"] == "1"

def test_lookup_by_multiple_short_ids(self) -> None:
group = self.group
group2 = self.create_group()

self.login_as(user=self.user)
response = self.get_success_response(
query=f"{group.qualified_short_id} {group2.qualified_short_id}", shortIdLookup=1
)
assert {r["id"] for r in response.data} == {str(group.id), str(group2.id)}
assert response.get("X-Sentry-Direct-Hit") != "1"

def test_lookup_by_group_id(self) -> None:
self.login_as(user=self.user)
response = self.get_success_response(group=self.group.id)
Expand Down
12 changes: 12 additions & 0 deletions tests/snuba/api/endpoints/test_project_group_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ def test_lookup_by_short_id(self) -> None:
response = self.client.get(f"{self.path}?query={short_id}&shortIdLookup=1", format="json")
assert response.status_code == 200
assert len(response.data) == 1
assert response["X-Sentry-Direct-Hit"] == "1"

def test_lookup_by_multiple_short_ids(self) -> None:
group = self.group
group2 = self.create_group(project=self.project)

self.login_as(user=self.user)
query = f"{group.qualified_short_id} {group2.qualified_short_id}"
response = self.client.get(f"{self.path}?query={query}&shortIdLookup=1", format="json")
assert response.status_code == 200
assert {r["id"] for r in response.data} == {str(group.id), str(group2.id)}
assert response.get("X-Sentry-Direct-Hit") != "1"

def test_lookup_by_short_id_no_perms(self) -> None:
organization = self.create_organization()
Expand Down
Loading