Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions src/sentry/search/events/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ class ThresholdDict(TypedDict):
"timestamp",
"timestamp.to_hour",
"timestamp.to_day",
"error.received",
}
NON_FAILURE_STATUS = {"ok", "cancelled", "unknown"}
HTTP_SERVER_ERROR_STATUS = {
Expand Down
9 changes: 3 additions & 6 deletions src/sentry/search/events/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
SEMVER_PACKAGE_ALIAS,
SEMVER_WILDCARDS,
TEAM_KEY_TRANSACTION_ALIAS,
TIMESTAMP_FIELDS,
TRANSACTION_STATUS_ALIAS,
USER_DISPLAY_ALIAS,
)
Expand Down Expand Up @@ -578,13 +579,9 @@ def convert_search_filter_to_snuba_query(
elif name in ARRAY_FIELDS and search_filter.value.raw_value == "":
return [["notEmpty", [name]], "=", 1 if search_filter.operator == "!=" else 0]
else:
# timestamp{,.to_{hour,day}} need a datetime string
# TIMESTAMP_FIELDS need a datetime string
# last_seen needs an integer
if isinstance(value, datetime) and name not in {
"timestamp",
"timestamp.to_hour",
"timestamp.to_day",
}:
if isinstance(value, datetime) and name not in TIMESTAMP_FIELDS:
value = int(value.timestamp()) * 1000

if name in {"trace.span", "trace.parent_span"}:
Expand Down
47 changes: 46 additions & 1 deletion tests/sentry/search/events/builder/test_errors.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from __future__ import annotations

from datetime import datetime, timedelta, timezone

import pytest
from snuba_sdk import Entity, Join, Relationship
from snuba_sdk.column import Column
from snuba_sdk.conditions import Condition, Op
from snuba_sdk.function import Function

from sentry.search.events.builder.errors import ErrorsQueryBuilder
from sentry.search.events.types import QueryBuilderConfig
from sentry.search.events.types import QueryBuilderConfig, SnubaParams
from sentry.snuba.dataset import Dataset
from sentry.snuba.errors import PARSER_CONFIG_OVERRIDES
from sentry.testutils.cases import TestCase
Expand Down Expand Up @@ -121,3 +123,46 @@ def test_is_status_simple_query(self) -> None:
self.projects,
),
]

def test_error_received_filter_uses_datetime(self) -> None:
"""Test that error.received filter uses datetime comparison, not epoch integer."""
now = datetime.now(timezone.utc)
start = now - timedelta(days=1)
end = now
filter_date = now - timedelta(hours=12)

query = ErrorsQueryBuilder(
dataset=Dataset.Events,
query=f"error.received:>{filter_date.isoformat()}",
selected_columns=["count()"],
params={
"project_id": self.projects,
},
snuba_params=SnubaParams(
start=start,
end=end,
projects=[self.project],
organization=self.organization,
),
offset=None,
limit=None,
config=QueryBuilderConfig(),
).get_snql_query()
query.validate()

# Find the error.received condition in the where clause
received_conditions = [
c
for c in query.query.where
if isinstance(c, Condition) and isinstance(c.lhs, Column) and c.lhs.name == "received"
]

assert len(received_conditions) == 1, "Should have exactly one error.received condition"
received_condition = received_conditions[0]

# Verify the condition uses datetime comparison, not epoch integer
assert received_condition.op == Op.GT
# The RHS should be a datetime or datetime-formatted Function, not an integer
assert not isinstance(
received_condition.rhs, int
), "error.received should compare against datetime, not epoch integer"
47 changes: 47 additions & 0 deletions tests/snuba/api/endpoints/test_organization_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -7339,3 +7339,50 @@ def test_remapping(self) -> None:
assert meta["fields"]["transaction.duration"] == "duration"
assert meta["units"]["span.duration"] == "millisecond"
assert meta["units"]["transaction.duration"] == "millisecond"

def test_error_received_filter(self) -> None:
"""Test that error.received filter works correctly with datetime comparison."""
# Store an event 10 minutes ago
self.store_event(
data={
"event_id": "a" * 32,
"timestamp": self.ten_mins_ago_iso,
"fingerprint": ["group1"],
"message": "older event",
},
project_id=self.project.id,
)

# Store an event 9 minutes ago
nine_mins_ago_iso = self.nine_mins_ago.replace(microsecond=0).isoformat()
self.store_event(
data={
"event_id": "b" * 32,
"timestamp": nine_mins_ago_iso,
"fingerprint": ["group2"],
"message": "newer event",
},
project_id=self.project.id,
)

# Query for events received after 10 mins ago (should only get the newer one)
query = {
"field": ["count()", "message"],
"statsPeriod": "1h",
"query": f"error.received:>{self.ten_mins_ago_iso}",
"dataset": "errors",
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert response.data["data"][0]["count()"] == 1

# Query for events received after 11 mins ago (should get both)
query = {
"field": ["count()"],
"statsPeriod": "1h",
"query": f"error.received:>{self.eleven_mins_ago_iso}",
"dataset": "errors",
}
response = self.do_request(query)
assert response.status_code == 200, response.content
assert response.data["data"][0]["count()"] == 2
Loading