Skip to content

Commit b35c797

Browse files
authored
fix(issues): Fix issue search single page count inaccuracy (#103865)
Our issues search pagination uses some heuristics to give the total results number, but this breaks for single page results sometimes which makes no sense. Added exception for the single page case to just use the number of results and avoid this.
1 parent 95574a9 commit b35c797

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

src/sentry/issues/endpoints/organization_group_index.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,18 @@ def get(self, request: Request, organization: Organization) -> Response:
389389
status_labels = {QUERY_STATUS_LOOKUP[s] for s in status[0].value.raw_value}
390390
context = [r for r in context if "status" not in r or r["status"] in status_labels]
391391

392+
# Sanity check: if we're on the first and last page with no more results,
393+
# the estimated hits from sampling may be too high due to Snuba/Postgres
394+
# data inconsistency. Cap hits to match the actual number of results.
395+
if (
396+
cursor_result.hits is not None
397+
and cursor_result.next.has_results is False
398+
and not request.GET.get("cursor")
399+
):
400+
actual_count = len(context)
401+
if cursor_result.hits > actual_count:
402+
cursor_result.hits = actual_count
403+
392404
response = Response(context)
393405

394406
self.add_cursor_headers(request, response, cursor_result)

tests/sentry/issues/endpoints/test_organization_group_index.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,45 @@ def _my_patched_params(
946946

947947
assert options.set("snuba.search.hits-sample-size", old_sample_size)
948948

949+
@patch("sentry.search.snuba.executors.PostgresSnubaQueryExecutor.calculate_hits")
950+
def test_hits_capped_when_overestimated(self, mock_calculate_hits: MagicMock) -> None:
951+
"""
952+
Test that when sampling overestimates the hit count and all results fit on one page,
953+
the X-Hits header is capped to the actual number of results returned.
954+
955+
This prevents UI bugs like showing "(6-11) of 11" when there are only 6 results.
956+
"""
957+
self.login_as(user=self.user)
958+
959+
# Create 6 groups
960+
groups = []
961+
for i in range(6):
962+
event = self.store_event(
963+
data={
964+
"timestamp": before_now(days=i).isoformat(),
965+
"fingerprint": [f"group-{i}"],
966+
},
967+
project_id=self.project.id,
968+
)
969+
groups.append(event.group)
970+
971+
# Mock calculate_hits to return an overestimate (simulating sampling inaccuracy)
972+
# This would happen when Snuba thinks there are 11 groups but Postgres only has 6
973+
mock_calculate_hits.return_value = 11
974+
975+
# Make a request that returns all 6 groups on one page
976+
response = self.get_success_response(limit=25, query="is:unresolved")
977+
978+
# Should return all 6 groups
979+
assert len(response.data) == 6
980+
981+
# X-Hits should be corrected to 6, not the overestimated 11
982+
assert response["X-Hits"] == "6"
983+
984+
# Verify no next page exists (we have all results)
985+
links = self._parse_links(response["Link"])
986+
assert links["next"]["results"] == "false"
987+
949988
def test_assigned_me_none(self) -> None:
950989
self.login_as(user=self.user)
951990
groups = []

0 commit comments

Comments
 (0)