Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1a4fdd4
fix(navigation): show pending invite dot on the organization row
a-lider Sep 30, 2026
be10012
fix(navigation): explain the pending invite dot on hover
a-lider Sep 30, 2026
fce88d1
chore(visual-review): steer authors to approve quarantined changes
webjunkie Oct 1, 2026
0b3058b
fix(navigation): polish the pending invite dot and organization row
a-lider Oct 1, 2026
e848dc8
chore(visual-review): cover unchanged fixes, removals and post-fix lifts
webjunkie Oct 1, 2026
12fc073
feat(visual-review): lift a quarantine when the fixing pr merges
webjunkie Oct 1, 2026
26320aa
feat(visual-review): request a quarantine lift by story identifier
webjunkie Oct 1, 2026
b6e102d
fix(visual-review): serialize lift requests and stop retrying past th…
webjunkie Oct 1, 2026
ff9f2d1
Merge remote-tracking branch 'origin/master' into posthog/pending-inv…
a-lider Oct 1, 2026
3a2dd3d
chore: update OpenAPI generated types
tests-posthog[bot] Oct 1, 2026
0fc3d0e
fix(navigation): keep pending invites reachable on project pending de…
a-lider Oct 1, 2026
40b7b16
fix(visual-review): match lift requests to the active quarantine in t…
webjunkie Oct 1, 2026
2f40557
Merge remote-tracking branch 'origin/feat/vr-quarantine-lift-on-merge…
webjunkie Oct 1, 2026
7223c34
chore(visual-review): tighten lift and removal checks in the triage s…
webjunkie Oct 1, 2026
2376cd8
Merge branch 'docs/vr-quarantined-story-changes' into feat/vr-quarant…
webjunkie Oct 1, 2026
f8ae6fb
chore(visual-review): require several clean runs before a manual lift
webjunkie Oct 1, 2026
7c413f1
Merge branch 'docs/vr-quarantined-story-changes' into resign-feat
webjunkie Oct 1, 2026
6f2280e
fix(visual-review): address lift-on-merge review findings
webjunkie Oct 1, 2026
3b7ac58
fix(slack_app): stop agent design status appends once slack ends the …
posthog[bot] Oct 1, 2026
e1e5c0d
chore(slack_app): name the relay stream patch like the other task pat…
posthog[bot] Oct 1, 2026
9a980a3
chore(visual-review): note that a quarantine also ends at its expiry
webjunkie Oct 1, 2026
ba9e5b1
Merge branch 'docs/vr-quarantined-story-changes' into feat/vr-quarant…
webjunkie Oct 1, 2026
d164547
fix(visual-review): verify the lift picture and quarantine under thei…
webjunkie Oct 1, 2026
923aca1
fix(visual-review): reload lift requests after a failed cancel
webjunkie Oct 1, 2026
9724be1
fix(slack_app): deliver a turn's attachments when slack closed its st…
posthog[bot] Oct 1, 2026
7a00340
fix(visual-review): wrap lift tool output as untrusted data
webjunkie Oct 1, 2026
e66e76f
chore: update OpenAPI generated types
tests-posthog[bot] Oct 1, 2026
e2a9076
fix(visual-review): render the lift-on-merge story before the dialog …
webjunkie Oct 1, 2026
c38c910
Merge remote-tracking branch 'origin/master' into feat/vr-quarantine-…
webjunkie Oct 1, 2026
34311d1
chore(visual): update storybook baselines
posthog[bot] Oct 1, 2026
8348f10
fix(visual-review): declare the lift story fixtures before the story
webjunkie Oct 1, 2026
02bcd3c
chore(today): pin the clock in the first-open briefing test
aspicer Oct 2, 2026
4415adb
fix(billing): default usage and spend to complete days
pawel-cebula Sep 28, 2026
f5dd0d3
fix(billing): restore all-date guidance in tool schemas
pawel-cebula Sep 28, 2026
fb3261b
chore(tasks): bump sandbox @posthog/agent to 2.4.253
scheduled-actions-posthog[bot] Oct 2, 2026
5b5a95b
test(today): cover a call without a timezone on a different briefing day
aspicer Oct 2, 2026
d4a8664
chore(billing): regenerate export date guidance after rebase
pawel-cebula Oct 2, 2026
dfcad1f
chore(visual): update storybook baselines
posthog[bot] Oct 2, 2026
a02d113
feat(tasks): pin comments on images and comment on selected text
puemos Oct 2, 2026
11333e7
fix(tasks): keep comment highlights on their text in full page
puemos Oct 2, 2026
70ebb1b
Merging 5b5a95b3b43d4dd4a59b999a0408d72e25008e1d into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
b3a4c03
Merging 34311d142baf574566dc8d0534421d79168c882d into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
8d7473f
Merging b3a4c03d264aa17bdcfb7a4a321af60403ae7005 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
d1830d4
Merging 70ebb1b4778f4e1a7bfe5ec3c375d3eaa4160298 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
6cb0538
Merging fb3261b472eb34c8f03c918c88107ac2d1833d5b into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
2d48d1f
Merging d4a8664d7001b74acadb557b5b11eb7e023ee0eb into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
7b59609
Merging 2d48d1fd0d538e8da43b437d31bf64b1023f27cb into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
db0007c
Merging 11333e70bc44608ea397aaa0681e260397057f42 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
3cb7ed6
Merging dfcad1f3ca583a60ce25bf5d24eb21df3e6c6732 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
954eb79
Merging 9724be17c2d9de3d33c1dce8ab8bdc22a4b5e023 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
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: 2 additions & 0 deletions .semgrep/rules/security/idor-team-scoped-models.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ rules:
|ProjectSecretAPIKey
|PropertyAccessControl
|PullRequestAudience
|QuarantineLiftRequest
|QuarantinedIdentifier
|QueryTabState
|QueueJob
Expand Down Expand Up @@ -755,6 +756,7 @@ rules:
|ProjectSecretAPIKey
|PropertyAccessControl
|PullRequestAudience
|QuarantineLiftRequest
|QuarantinedIdentifier
|QueryTabState
|QueueJob
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ a billing-specific tool wrapper.
The billing usage/spend tools accept `usage_types` as an array of strings.
Their field description lists the accepted identifiers from `ee/billing/billing_types.py`, through the generated API schema.
The MCP client JSON-encodes the array for the HTTP API.
When both dates are omitted, the shared billing request serializer defaults usage/spend reads to the last 30 complete UTC days, ending yesterday.
Explicit date ranges are unchanged; a start date without an end date still ends today.
This also applies to the organization usage/spend time-series endpoints and CSV exports.
The billing overview, usage, and spend tools do not need a rollout flag.
API scopes and billing access checks still apply.

Expand Down
28 changes: 24 additions & 4 deletions ee/api/billing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import json
from collections.abc import Callable, Sequence
from datetime import timedelta
from typing import Any, NoReturn, Optional, cast
from zoneinfo import ZoneInfo

Expand Down Expand Up @@ -351,8 +352,23 @@ class BillingUsageRequestSerializer(serializers.Serializer):
Only responsible for parsing dates, passes through other params.
"""

start_date = serializers.CharField(required=False, allow_blank=True, allow_null=True)
end_date = serializers.CharField(required=False, allow_blank=True, allow_null=True)
start_date = serializers.CharField(
required=False,
allow_blank=True,
allow_null=True,
help_text=(
'Start date (YYYY-MM-DD, UTC), or "all" for 2020-01-01. If both dates are omitted, defaults to 30 days ago.'
),
)
end_date = serializers.CharField(
required=False,
allow_blank=True,
allow_null=True,
help_text=(
"End date (YYYY-MM-DD, UTC), inclusive. Defaults to yesterday if both dates are omitted, "
"or today if only start_date is provided."
),
)
usage_types = serializers.CharField(
required=False,
allow_blank=True,
Expand Down Expand Up @@ -436,8 +452,12 @@ def validate_end_date(self, value: Optional[str]) -> Optional[str]:
return self._parse_date(value, "end_date")

def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
if attrs.get("start_date") and not attrs.get("end_date"):
attrs["end_date"] = timezone.now().date().isoformat()
today_utc = timezone.now().astimezone(ZoneInfo("UTC")).date()
if not attrs.get("start_date") and not attrs.get("end_date"):
attrs["start_date"] = (today_utc - timedelta(days=30)).isoformat()
attrs["end_date"] = (today_utc - timedelta(days=1)).isoformat()
elif attrs.get("start_date") and not attrs.get("end_date"):
attrs["end_date"] = today_utc.isoformat()
return attrs

def validate_usage_types(self, value: Optional[str]) -> Optional[str]:
Expand Down
44 changes: 39 additions & 5 deletions ee/api/test/test_billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ def test_every_billing_action_is_partner_locked_or_explicitly_exempt(self) -> No
assert unlocked == self.READ_ONLY_ACTIONS | self.UNLOCKED_WRITE_ACTIONS


class TestBillingUsageRequestSerializer(TestCase):
class TestBillingUsageRequestSerializer(SimpleTestCase):
def test_valid_dates(self):
serializer = BillingUsageRequestSerializer(data={"start_date": "2025-01-01", "end_date": "2025-01-31"})
self.assertTrue(serializer.is_valid(), serializer.errors)
Expand Down Expand Up @@ -1423,11 +1423,30 @@ def test_accepts_every_breakdown_the_spend_read_serves(self, _case_name: str, va
serializer = BillingUsageRequestSerializer(data={"breakdowns": value})
self.assertTrue(serializer.is_valid(), serializer.errors)

def test_empty_and_null_dates_are_valid(self):
serializer = BillingUsageRequestSerializer(data={"start_date": "", "end_date": None})
@parameterized.expand(
[
("missing", {}),
("empty", {"start_date": "", "end_date": ""}),
("null", {"start_date": None, "end_date": None}),
("empty_start", {"start_date": ""}),
("null_end", {"end_date": None}),
]
)
@time_machine.travel("2025-02-15T00:30:00+14:00", tick=False)
def test_missing_empty_and_null_dates_default_to_last_30_complete_utc_days(
self, _case_name: str, data: dict[str, str | None]
) -> None:
serializer = BillingUsageRequestSerializer(data=data)
self.assertTrue(serializer.is_valid(), serializer.errors)
self.assertEqual(serializer.validated_data["start_date"], "2025-01-15")
self.assertEqual(serializer.validated_data["end_date"], "2025-02-13")

@time_machine.travel("2025-02-15", tick=False)
def test_end_date_without_start_date_is_preserved(self) -> None:
serializer = BillingUsageRequestSerializer(data={"end_date": "2025-02-14"})
self.assertTrue(serializer.is_valid(), serializer.errors)
self.assertIsNone(serializer.validated_data.get("start_date"))
self.assertIsNone(serializer.validated_data.get("end_date"))
self.assertNotIn("start_date", serializer.validated_data)
self.assertEqual(serializer.validated_data["end_date"], "2025-02-14")


class TestBillingUpstreamValidationErrors(SimpleTestCase):
Expand Down Expand Up @@ -1617,6 +1636,21 @@ def test_get_usage_success(self, mock_get_usage_data):
# No teams_map: names are put into the response on the way out.
self.assertNotIn("teams_map", passed_params)

@parameterized.expand([("usage",), ("spend",)])
@time_machine.travel("2025-02-15T00:30:00+14:00", tick=False)
def test_usage_and_spend_default_date_range_is_sent_to_billing(self, endpoint: str) -> None:
manager_method = f"ee.billing.billing_manager.BillingManager.get_{endpoint}_data"
mock_data = self.MOCK_USAGE_DATA if endpoint == "usage" else self.MOCK_SPEND_DATA

with patch(manager_method, return_value=mock_data) as mock_fetch:
response = self.client.get(f"/api/billing/{endpoint}/")

self.assertEqual(response.status_code, status.HTTP_200_OK)
mock_fetch.assert_called_once()
passed_params = mock_fetch.call_args[0][1]
self.assertEqual(passed_params["start_date"], "2025-01-15")
self.assertEqual(passed_params["end_date"], "2025-02-13")

@staticmethod
def _billing_refusal(upstream_status: int, body: object) -> Exception:
# The shape handle_billing_service_error raises: the status in the message, the parsed body third.
Expand Down
4 changes: 4 additions & 0 deletions frontend/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13158,6 +13158,10 @@ snapshots:
hash: v1.k794b7964.12bc6373464176d7fb6b9251bb7d6342cd381a64e3ee28c8380045b06f5e97ec.BLZ-Fbl_W9xx4oAA39xVucIxMtSpBAzm5Pwp6f1Bk6w
scenes-app-visual-review-flakiness--unstable--light:
hash: v1.k794b7964.26ff0c274624f9d1ea71f4377d104d5e4feae204ba8ea0b68735c7f5fae8423e.WaXT0zPJ1WHOerBWKkJmHERCIZwpJT_L_4AHvWL3bSE
scenes-app-visual-review-run--quarantined-snapshot-lifts-on-merge--dark:
hash: v1.k794b7964.758a0e95ef8736a1c6ff3da08d559f93ae3766fb625dafe72470b69ae617af46.JMuycNcdRT6ufB9VBSNhhKJlVUq35qcQNYS7rva0Wxk
scenes-app-visual-review-run--quarantined-snapshot-lifts-on-merge--light:
hash: v1.k794b7964.82313ef9f43904aaaee7dfb013a6949e0418f8a3094dbadfd8494d493199ee45.gOZXPTz4C9RAb_lA9fIwVXaLe_bO7UhAKa051YafvR8
scenes-app-visual-review-run--ready-to-finalize--dark:
hash: v1.k794b7964.2ddf6d3c5eea469705d81ec0489b029e21e3d06d119b0365ddebe1340677e69c.MPAF7Dn_bpbKyhuQ2l_aoj6czH6TKBlFE2nXSKVqx4I
scenes-app-visual-review-run--ready-to-finalize--light:
Expand Down
16 changes: 16 additions & 0 deletions products/billing/frontend/generated/api.schemas.ts

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

32 changes: 0 additions & 32 deletions products/billing/mcp/tools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -264,14 +264,6 @@ tools:
grant applies only to usage/spend, not billing-overview-get or billing admin actions. Use this to
investigate cost spikes, compare spend across periods, or attribute spend to specific products or teams.
param_overrides:
start_date:
description: >
Start date (YYYY-MM-DD). For open-ended investigations, choose an explicit recent window such as the
last 30 days. If you use "all", also pass end_date.
end_date:
description: >
End date (YYYY-MM-DD), inclusive. Pass this whenever start_date is set; use today's date if the user
did not name one.
team_ids:
input_schema: BillingTeamIdsSchema
usage_types:
Expand Down Expand Up @@ -333,14 +325,6 @@ tools:
billing-usage-timeseries-get. Beta: behind the organization-billing-api flag, and the output shape may
change while it is.
param_overrides:
start_date:
description: >
Start date (YYYY-MM-DD). For open-ended investigations, choose an explicit recent window such as the
last 30 days. If you use "all", also pass end_date.
end_date:
description: >
End date (YYYY-MM-DD), inclusive. Pass this whenever start_date is set; use today's date if the user
did not name one.
team_ids:
description: >
JSON-encoded array of numeric team (project) IDs to filter by, NOT a comma-separated string. Pass as
Expand Down Expand Up @@ -432,14 +416,6 @@ tools:
projects. For a single-snapshot aggregate view call billing-overview-get when the caller has full billing
access; for cost (spend) breakdowns call billing-spend-get.
param_overrides:
start_date:
description: >
Start date (YYYY-MM-DD). For open-ended investigations, choose an explicit recent window such as the
last 30 days. If you use "all", also pass end_date.
end_date:
description: >
End date (YYYY-MM-DD), inclusive. Pass this whenever start_date is set; use today's date if the user
did not name one.
team_ids:
input_schema: BillingTeamIdsSchema
usage_types:
Expand Down Expand Up @@ -535,14 +511,6 @@ tools:
billing-spend-timeseries-get. Beta: behind the organization-billing-api flag, and the output shape may
change while it is.
param_overrides:
start_date:
description: >
Start date (YYYY-MM-DD). For open-ended investigations, choose an explicit recent window such as the
last 30 days. If you use "all", also pass end_date.
end_date:
description: >
End date (YYYY-MM-DD), inclusive. Pass this whenever start_date is set; use today's date if the user
did not name one.
team_ids:
description: >
JSON-encoded array of numeric team (project) IDs to filter by, NOT a comma-separated string. Pass as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -846,3 +846,8 @@ export const MarkdownComments: Story = {
parameters: { msw: { mocks: commentMocks() } },
render: () => <StoryPage fileName={REPORT_FILE_NAME} commentsOpen />,
}

export const ImageCommentPins: Story = {
parameters: { msw: { mocks: commentMocks() } },
render: () => <StoryPage fileName="trial-starts-by-step.svg" commentsOpen />,
}
Loading
Loading