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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ dependencies = [
"sentry-forked-email-reply-parser>=0.5.12.post1",
"sentry-kafka-schemas>=2.1.27",
"sentry-ophio>=1.1.3",
"sentry-protos>=0.8.30",
"sentry-protos>=0.8.31",
"sentry-redis-tools>=0.5.0",
"sentry-relay>=0.9.27",
"sentry-scm==0.10.1",
Expand Down
19 changes: 16 additions & 3 deletions src/sentry/billing/platform/services/usage/_outcomes_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,16 @@ def query_outcomes_usage(request: GetUsageRequest) -> GetUsageResponse:
sample_rate=1.0,
)

return _build_response(rows)
last_usage_ts: datetime | None = None
for row in rows:
row_max = row.get("max_ts")
if not row_max:
continue
parsed = datetime.fromisoformat(row_max).replace(tzinfo=timezone.utc)
if last_usage_ts is None or parsed > last_usage_ts:
last_usage_ts = parsed

return _build_response(rows, last_usage_ts)


def _build_query(
Expand All @@ -102,6 +111,7 @@ def _build_query(
select=[
Column("category"),
Column("time"),
Function("max", [Column("timestamp")], "max_ts"),
_total_function(total_outcomes),
Function(
"sumIf",
Expand Down Expand Up @@ -165,7 +175,7 @@ def _build_query(
)


def _build_response(rows: list[dict]) -> GetUsageResponse:
def _build_response(rows: list[dict], last_usage_ts: datetime | None) -> GetUsageResponse:
# Two-level accumulator: days_map[day_str][category_id] -> usage fields.
# Each row already contains all 7 sumIf-aggregated fields from ClickHouse.
#
Expand Down Expand Up @@ -200,7 +210,10 @@ def _build_response(rows: list[dict]) -> GetUsageResponse:
]
days.append(DailyUsage(date=date, usage=usage))

return GetUsageResponse(days=days, seats=[])
response = GetUsageResponse(days=days, seats=[])
if last_usage_ts is not None:
response.last_usage_ts.FromDatetime(last_usage_ts)
return response


def _total_function(outcomes: Sequence[int] | None) -> Function:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def test_inner_or_includes_usage_exceeded_and_grace_period(self):

class TestBuildResponse:
def test_build_response_empty(self):
response = _build_response([])
response = _build_response([], None)

assert isinstance(response, GetUsageResponse)
assert list(response.days) == []
Expand All @@ -97,7 +97,7 @@ def test_build_response_multi_day(self):
_make_row(time="2025-03-15T00:00:00+00:00", accepted=100, total=100),
_make_row(time="2025-03-16T00:00:00+00:00", accepted=75, total=75),
]
response = _build_response(rows)
response = _build_response(rows, None)

assert len(response.days) == 3
assert response.days[0].date == Date(year=2025, month=3, day=15)
Expand All @@ -110,7 +110,7 @@ def test_build_response_multi_category(self):
_make_row(time="2025-03-15T00:00:00+00:00", category=1, accepted=100, total=100),
_make_row(time="2025-03-15T00:00:00+00:00", category=9, accepted=25, total=25),
]
response = _build_response(rows)
response = _build_response(rows, None)

assert len(response.days) == 1
day = response.days[0]
Expand All @@ -136,7 +136,7 @@ def test_build_response_preserves_overlapping_semantics(self):
dynamic_sampling=0,
)
]
response = _build_response(rows)
response = _build_response(rows, None)

data = response.days[0].usage[0].data
assert data.dropped == 175
Expand All @@ -158,7 +158,7 @@ def test_build_response_all_fields(self):
dynamic_sampling=0,
),
]
response = _build_response(rows)
response = _build_response(rows, None)

assert len(response.days) == 1
data = response.days[0].usage[0].data
Expand Down Expand Up @@ -287,6 +287,7 @@ def test_build_query_select_has_sumif_columns(self):
assert aliases == [
"category",
"time",
"max_ts",
"total",
"accepted",
"dropped",
Expand Down Expand Up @@ -342,6 +343,7 @@ def test_query_returns_response(self, mock_query):
{
"time": "2025-03-15T00:00:00+00:00",
"category": 1,
"max_ts": "2025-03-15T11:00:00+00:00",
"total": 200,
"accepted": 200,
"dropped": 0,
Expand Down
6 changes: 3 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading