mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge 71757ab665 into 6fa0477d84
This commit is contained in:
commit
a40b725416
14 changed files with 464 additions and 26 deletions
|
|
@ -5222,20 +5222,36 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"in": "query",
|
||||
"name": "timezone",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 840,
|
||||
"minimum": -840,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"in": "query",
|
||||
"name": "include_current_utc_day",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"title": "Include Current Utc Day",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -5395,20 +5411,36 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"in": "query",
|
||||
"name": "timezone",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 840,
|
||||
"minimum": -840,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"in": "query",
|
||||
"name": "include_current_utc_day",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"title": "Include Current Utc Day",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -5577,20 +5609,36 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"in": "query",
|
||||
"name": "timezone",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 840,
|
||||
"minimum": -840,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"in": "query",
|
||||
"name": "include_current_utc_day",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"title": "Include Current Utc Day",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -5749,20 +5797,36 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"in": "query",
|
||||
"name": "timezone",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 840,
|
||||
"minimum": -840,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"in": "query",
|
||||
"name": "include_current_utc_day",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"title": "Include Current Utc Day",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -5922,20 +5986,36 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"in": "query",
|
||||
"name": "timezone",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"maximum": 840,
|
||||
"minimum": -840,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
"title": "Timezone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"in": "query",
|
||||
"name": "include_current_utc_day",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"description": "When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended.",
|
||||
"title": "Include Current Utc Day",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
|
|||
from litellm.proxy.utils import PrismaClient, get_prisma_client_or_throw
|
||||
from litellm.repositories.daily_activity_buckets import api_key_bucket
|
||||
from litellm.repositories.daily_activity_repository import DailyActivityRepository
|
||||
from litellm.repositories.daily_activity_sql import adjust_dates_for_timezone
|
||||
from litellm.router import Router
|
||||
from litellm.types.proxy.management_endpoints.common_daily_activity import (
|
||||
CacheLeakageKeysResponse,
|
||||
|
|
@ -83,11 +84,22 @@ def get_daily_activity_llm_router() -> Router | None:
|
|||
|
||||
|
||||
def _date_range_error(query: EntityQuery, *, user_aggregated: bool) -> InvalidDateRange | None:
|
||||
if user_aggregated:
|
||||
date_range: Final = parse_canonical_date_range(query.start_date, query.end_date)
|
||||
return date_range if isinstance(date_range, InvalidDateRange) else None
|
||||
if query.timezone_offset_minutes is not None and not (-840 <= query.timezone_offset_minutes <= 840):
|
||||
return InvalidDateRange(reason="timezone must be between -840 and 840 minutes")
|
||||
|
||||
range_error: Final[str | None] = aggregated_date_range_error(query.start_date, query.end_date)
|
||||
date_range: Final = parse_canonical_date_range(query.start_date, query.end_date)
|
||||
if isinstance(date_range, InvalidDateRange):
|
||||
return date_range
|
||||
if user_aggregated:
|
||||
return None
|
||||
|
||||
adjusted_start, adjusted_end = adjust_dates_for_timezone(
|
||||
date_range.start.isoformat(),
|
||||
date_range.end.isoformat(),
|
||||
query.timezone_offset_minutes,
|
||||
query.include_current_utc_day,
|
||||
)
|
||||
range_error: Final = aggregated_date_range_error(adjusted_start, adjusted_end)
|
||||
return None if range_error is None else InvalidDateRange(reason=range_error)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -228,11 +228,13 @@ def _user_query(
|
|||
),
|
||||
timezone: int | None = Query(
|
||||
default=None,
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC (e.g., 480 for PST). "
|
||||
"Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
),
|
||||
include_current_utc_day: bool = Query(
|
||||
default=False,
|
||||
default=True,
|
||||
description="When the range ends on the caller's current local day, extend it to "
|
||||
"today's UTC bucket so spend written after the caller's local midnight (in UTC "
|
||||
"terms) is included. Requires the timezone parameter. Historical ranges are "
|
||||
|
|
@ -258,7 +260,20 @@ def _team_query(
|
|||
model: str | None = None,
|
||||
api_key: str | None = None,
|
||||
exclude_team_ids: str | None = None,
|
||||
timezone: int | None = None,
|
||||
timezone: int | None = Query(
|
||||
default=None,
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC (e.g., 480 for PST). "
|
||||
"Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
),
|
||||
include_current_utc_day: bool = Query(
|
||||
default=True,
|
||||
description="When the range ends on the caller's current local day, extend it to "
|
||||
"today's UTC bucket so spend written after the caller's local midnight (in UTC "
|
||||
"terms) is included. Requires the timezone parameter. Historical ranges are "
|
||||
"never extended.",
|
||||
),
|
||||
) -> EntityQuery:
|
||||
return EntityQuery(
|
||||
entity_ids=_query_ids(team_ids),
|
||||
|
|
@ -268,7 +283,7 @@ def _team_query(
|
|||
end_date=end_date,
|
||||
model=model,
|
||||
timezone_offset_minutes=timezone,
|
||||
include_current_utc_day=False,
|
||||
include_current_utc_day=include_current_utc_day,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -278,7 +293,20 @@ def _tag_query(
|
|||
model: str | None = None,
|
||||
api_key: str | None = None,
|
||||
tags: str | None = None,
|
||||
timezone: int | None = None,
|
||||
timezone: int | None = Query(
|
||||
default=None,
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC (e.g., 480 for PST). "
|
||||
"Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
),
|
||||
include_current_utc_day: bool = Query(
|
||||
default=True,
|
||||
description="When the range ends on the caller's current local day, extend it to "
|
||||
"today's UTC bucket so spend written after the caller's local midnight (in UTC "
|
||||
"terms) is included. Requires the timezone parameter. Historical ranges are "
|
||||
"never extended.",
|
||||
),
|
||||
) -> EntityQuery:
|
||||
return EntityQuery(
|
||||
entity_ids=_query_ids(tags),
|
||||
|
|
@ -288,7 +316,7 @@ def _tag_query(
|
|||
end_date=end_date,
|
||||
model=model,
|
||||
timezone_offset_minutes=timezone,
|
||||
include_current_utc_day=False,
|
||||
include_current_utc_day=include_current_utc_day,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -299,7 +327,20 @@ def _organization_query(
|
|||
model: str | None = None,
|
||||
api_key: str | None = None,
|
||||
exclude_organization_ids: str | None = None,
|
||||
timezone: int | None = None,
|
||||
timezone: int | None = Query(
|
||||
default=None,
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC (e.g., 480 for PST). "
|
||||
"Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
),
|
||||
include_current_utc_day: bool = Query(
|
||||
default=True,
|
||||
description="When the range ends on the caller's current local day, extend it to "
|
||||
"today's UTC bucket so spend written after the caller's local midnight (in UTC "
|
||||
"terms) is included. Requires the timezone parameter. Historical ranges are "
|
||||
"never extended.",
|
||||
),
|
||||
) -> EntityQuery:
|
||||
return EntityQuery(
|
||||
entity_ids=_query_ids(organization_ids),
|
||||
|
|
@ -309,7 +350,7 @@ def _organization_query(
|
|||
end_date=end_date,
|
||||
model=model,
|
||||
timezone_offset_minutes=timezone,
|
||||
include_current_utc_day=False,
|
||||
include_current_utc_day=include_current_utc_day,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -320,7 +361,20 @@ def _customer_query(
|
|||
model: str | None = None,
|
||||
api_key: str | None = None,
|
||||
exclude_end_user_ids: str | None = None,
|
||||
timezone: int | None = None,
|
||||
timezone: int | None = Query(
|
||||
default=None,
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC (e.g., 480 for PST). "
|
||||
"Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
),
|
||||
include_current_utc_day: bool = Query(
|
||||
default=True,
|
||||
description="When the range ends on the caller's current local day, extend it to "
|
||||
"today's UTC bucket so spend written after the caller's local midnight (in UTC "
|
||||
"terms) is included. Requires the timezone parameter. Historical ranges are "
|
||||
"never extended.",
|
||||
),
|
||||
) -> EntityQuery:
|
||||
return EntityQuery(
|
||||
entity_ids=_query_ids(end_user_ids),
|
||||
|
|
@ -330,7 +384,7 @@ def _customer_query(
|
|||
end_date=end_date,
|
||||
model=model,
|
||||
timezone_offset_minutes=timezone,
|
||||
include_current_utc_day=False,
|
||||
include_current_utc_day=include_current_utc_day,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -341,7 +395,20 @@ def _agent_query(
|
|||
model: str | None = None,
|
||||
api_key: str | None = None,
|
||||
exclude_agent_ids: str | None = None,
|
||||
timezone: int | None = None,
|
||||
timezone: int | None = Query(
|
||||
default=None,
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC (e.g., 480 for PST). "
|
||||
"Matches JavaScript's Date.getTimezoneOffset() convention.",
|
||||
),
|
||||
include_current_utc_day: bool = Query(
|
||||
default=True,
|
||||
description="When the range ends on the caller's current local day, extend it to "
|
||||
"today's UTC bucket so spend written after the caller's local midnight (in UTC "
|
||||
"terms) is included. Requires the timezone parameter. Historical ranges are "
|
||||
"never extended.",
|
||||
),
|
||||
) -> EntityQuery:
|
||||
return EntityQuery(
|
||||
entity_ids=_query_ids(agent_ids),
|
||||
|
|
@ -351,7 +418,7 @@ def _agent_query(
|
|||
end_date=end_date,
|
||||
model=model,
|
||||
timezone_offset_minutes=timezone,
|
||||
include_current_utc_day=False,
|
||||
include_current_utc_day=include_current_utc_day,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from litellm.models.gateway_activity import GatewayRouteActivity, GatewayStatusC
|
|||
from litellm.proxy._types import CommonProxyErrors, LitellmUserRoles, UserAPIKeyAuth
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.deps.repositories import GatewayActivityRepositoryDep
|
||||
from litellm.repositories.daily_activity_sql import adjust_dates_for_timezone
|
||||
from litellm.types.proxy.gateway_requests import (
|
||||
GatewayRequestActivityResponse,
|
||||
GatewayRequestBreakdownEntry,
|
||||
|
|
@ -87,6 +88,21 @@ async def get_gateway_daily_activity(
|
|||
activity: GatewayActivityRepositoryDep,
|
||||
start_date: str | None = Query(default=None, description="Start date in YYYY-MM-DD format"),
|
||||
end_date: str | None = Query(default=None, description="End date in YYYY-MM-DD format"),
|
||||
timezone_offset_minutes: Annotated[
|
||||
int | None,
|
||||
Query(
|
||||
alias="timezone",
|
||||
ge=-840,
|
||||
le=840,
|
||||
description="Timezone offset in minutes from UTC, matching JavaScript's Date.getTimezoneOffset().",
|
||||
),
|
||||
] = None,
|
||||
include_current_utc_day: Annotated[
|
||||
bool,
|
||||
Query(
|
||||
description="Include the current UTC bucket when the range reaches the caller's local today. Requires timezone."
|
||||
),
|
||||
] = True,
|
||||
) -> GatewayRequestActivityResponse:
|
||||
"""
|
||||
Successful and failed gateway requests, counted at the ASGI edge.
|
||||
|
|
@ -109,7 +125,14 @@ async def get_gateway_daily_activity(
|
|||
default_start, default_end = _default_range()
|
||||
selected_start: Final = start_date or default_start
|
||||
selected_end: Final = end_date or default_end
|
||||
activity_rows: Final = await activity.get_activity(selected_start, selected_end)
|
||||
query_start, query_end = adjust_dates_for_timezone(
|
||||
selected_start,
|
||||
selected_end,
|
||||
timezone_offset_minutes,
|
||||
include_current_utc_day,
|
||||
utc_now=datetime.now(timezone.utc),
|
||||
)
|
||||
activity_rows: Final = await activity.get_activity(query_start, query_end)
|
||||
rows: Final = tuple(row for row in activity_rows if isinstance(row, GatewayRouteActivity))
|
||||
status_code_rows: Final = tuple(row for row in activity_rows if isinstance(row, GatewayStatusCodeActivity))
|
||||
verbose_proxy_logger.debug("/gateway/daily/activity - aggregated %d rows", len(rows))
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ PRISMA_TO_PG_TABLE: Final[Mapping[DailyActivityTable, str]] = MappingProxyType(
|
|||
)
|
||||
|
||||
|
||||
_MIN_TIMEZONE_OFFSET_MINUTES: Final = -840 # UTC+14 (e.g. Line Islands)
|
||||
_MAX_TIMEZONE_OFFSET_MINUTES: Final = 840 # UTC-14
|
||||
|
||||
|
||||
def adjust_dates_for_timezone(
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
|
|
@ -54,7 +58,11 @@ def adjust_dates_for_timezone(
|
|||
include_current_utc_day: bool = False,
|
||||
utc_now: datetime | None = None,
|
||||
) -> tuple[str, str]:
|
||||
if not include_current_utc_day or timezone_offset_minutes is None:
|
||||
if (
|
||||
not include_current_utc_day
|
||||
or timezone_offset_minutes is None
|
||||
or not (_MIN_TIMEZONE_OFFSET_MINUTES <= timezone_offset_minutes <= _MAX_TIMEZONE_OFFSET_MINUTES)
|
||||
):
|
||||
return start_date, end_date
|
||||
now: Final = utc_now if utc_now is not None else datetime.now(timezone.utc)
|
||||
caller_local_today: Final = (now - timedelta(minutes=timezone_offset_minutes)).date().isoformat()
|
||||
|
|
|
|||
|
|
@ -1557,6 +1557,32 @@ def test_user_aggregate_keeps_current_day_query_semantics(
|
|||
assert scope.include_current_utc_day is True
|
||||
|
||||
|
||||
def test_aggregate_routes_default_include_current_utc_day_to_true(
|
||||
daily_activity_client: tuple[TestClient, _FakeRepository],
|
||||
) -> None:
|
||||
client, repository = daily_activity_client
|
||||
for route in ("/user/daily/activity/aggregated", "/team/daily/activity/aggregated"):
|
||||
response: Final = client.get(
|
||||
route,
|
||||
params={**_DATE_PARAMS, "timezone": 480},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
scope: Final = repository.aggregated.call_args.args[0]
|
||||
assert scope.include_current_utc_day is True
|
||||
|
||||
|
||||
def test_aggregate_routes_reject_invalid_timezone(
|
||||
daily_activity_client: tuple[TestClient, _FakeRepository],
|
||||
) -> None:
|
||||
client, _ = daily_activity_client
|
||||
for route in ("/user/daily/activity/aggregated", "/team/daily/activity/aggregated"):
|
||||
response: Final = client.get(
|
||||
route,
|
||||
params={**_DATE_PARAMS, "timezone": 20000000},
|
||||
)
|
||||
assert response.status_code in (400, 422)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("start_date", "end_date", "message"),
|
||||
(
|
||||
|
|
|
|||
|
|
@ -71,6 +71,34 @@ def _prisma_returning(rows: Sequence[Mapping[str, str | int | None]] | None) ->
|
|||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gateway_daily_activity_client() -> TestClient:
|
||||
buckets: Final = (("2025-12-31", 3), ("2026-01-01", 7))
|
||||
|
||||
async def query_raw(_query: str, start_date: str, end_date: str) -> tuple[Mapping[str, str | int | None], ...]:
|
||||
return tuple(
|
||||
{
|
||||
"kind": "route",
|
||||
"date": date,
|
||||
"category": "llm",
|
||||
"route": "/chat/completions",
|
||||
"successful_requests": successful,
|
||||
"failed_requests": 0,
|
||||
"status_code": None,
|
||||
}
|
||||
for date, successful in buckets
|
||||
if start_date <= date <= end_date
|
||||
)
|
||||
|
||||
prisma: Final = MagicMock()
|
||||
prisma.db.query_raw = AsyncMock(side_effect=query_raw)
|
||||
app: Final = FastAPI()
|
||||
app.include_router(router)
|
||||
app.dependency_overrides[user_api_key_auth] = _admin
|
||||
app.dependency_overrides[get_gateway_activity_repository] = lambda: GatewayActivityRepository(prisma)
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
class TestDefaultRange:
|
||||
def test_spans_the_documented_lookback(self):
|
||||
start, end = _default_range()
|
||||
|
|
@ -307,6 +335,58 @@ class TestGatewayDailyActivityRoute:
|
|||
assert response.status_code == 200
|
||||
assert prisma.db.query_raw.call_args_list[0].args[1:] == _FROZEN_RANGE
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("utc_hour", "timezone_offset_minutes", "include_current_utc_day", "expected_successful_requests"),
|
||||
(
|
||||
pytest.param(12, None, True, 3, id="historical-without-timezone"),
|
||||
pytest.param(12, 0, True, 3, id="historical-utc"),
|
||||
pytest.param(12, -330, True, 3, id="historical-east"),
|
||||
pytest.param(12, 300, True, 3, id="historical-west"),
|
||||
pytest.param(1, 300, True, 10, id="live-west-year-rollover"),
|
||||
pytest.param(1, 300, None, 10, id="live-west-default"),
|
||||
pytest.param(1, 300, False, 3, id="live-west-opt-out"),
|
||||
pytest.param(1, None, True, 3, id="live-without-timezone"),
|
||||
pytest.param(1, 0, True, 3, id="historical-utc-after-midnight"),
|
||||
pytest.param(1, 840, True, 10, id="maximum-offset"),
|
||||
pytest.param(12, -840, True, 3, id="minimum-offset"),
|
||||
),
|
||||
)
|
||||
def test_timezone_queries_only_include_the_live_utc_bucket_for_the_callers_current_day(
|
||||
self,
|
||||
gateway_daily_activity_client: TestClient,
|
||||
utc_hour: int,
|
||||
timezone_offset_minutes: int | None,
|
||||
include_current_utc_day: bool | None,
|
||||
expected_successful_requests: int,
|
||||
) -> None:
|
||||
params: Final = {
|
||||
"start_date": "2025-12-31",
|
||||
"end_date": "2025-12-31",
|
||||
**(
|
||||
{"include_current_utc_day": str(include_current_utc_day).lower()}
|
||||
if include_current_utc_day is not None
|
||||
else {}
|
||||
),
|
||||
**({"timezone": str(timezone_offset_minutes)} if timezone_offset_minutes is not None else {}),
|
||||
}
|
||||
with patch("litellm.proxy.management_endpoints.gateway_request_endpoints.datetime") as clock:
|
||||
clock.now.return_value = datetime(2026, 1, 1, utc_hour, tzinfo=timezone.utc)
|
||||
response: Final = gateway_daily_activity_client.get("/gateway/daily/activity", params=params)
|
||||
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json()["total_successful_requests"] == expected_successful_requests
|
||||
|
||||
@pytest.mark.parametrize("timezone_offset_minutes", (-841, 841))
|
||||
def test_timezone_outside_supported_offsets_is_rejected(
|
||||
self, gateway_daily_activity_client: TestClient, timezone_offset_minutes: int
|
||||
) -> None:
|
||||
response: Final = gateway_daily_activity_client.get(
|
||||
"/gateway/daily/activity",
|
||||
params={"start_date": "2025-12-31", "end_date": "2025-12-31", "timezone": timezone_offset_minutes},
|
||||
)
|
||||
|
||||
assert response.status_code == 422, response.text
|
||||
|
||||
def test_serialized_response_carries_the_documented_shape(self):
|
||||
prisma: Final = _prisma_returning(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -263,6 +263,12 @@ def test_timezone_adjustment_live_end_handles_offset_and_opt_in_cases() -> None:
|
|||
assert adjust_dates_for_timezone(
|
||||
"2026-07-06", "2026-08-09", 420, include_current_utc_day=True, utc_now=pt_evening
|
||||
) == ("2026-07-06", "2026-08-09")
|
||||
assert adjust_dates_for_timezone(
|
||||
"2026-07-06", "2026-08-05", 20000000, include_current_utc_day=True, utc_now=pt_evening
|
||||
) == ("2026-07-06", "2026-08-05")
|
||||
assert adjust_dates_for_timezone(
|
||||
"2026-07-06", "2026-08-05", -900, include_current_utc_day=True, utc_now=pt_evening
|
||||
) == ("2026-07-06", "2026-08-05")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("offset_minutes", [None, 0, -330, 480])
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ export function useHomeUsage(): HomeUsage {
|
|||
const request = useMemo<DailyActivityRequest | null>(
|
||||
() =>
|
||||
accessToken && (isAdmin || userId)
|
||||
? { accessToken, ...range, entityIds: isAdmin ? null : [userId as string] }
|
||||
? {
|
||||
accessToken,
|
||||
...range,
|
||||
entityIds: isAdmin ? null : [userId as string],
|
||||
includeCurrentUtcDay: true,
|
||||
}
|
||||
: null,
|
||||
[accessToken, isAdmin, range, userId],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations, teamsLoadin
|
|||
startTime,
|
||||
endTime,
|
||||
entityIds: effectiveUserId ? [effectiveUserId] : null,
|
||||
includeCurrentUtcDay: true,
|
||||
}
|
||||
: null,
|
||||
[accessToken, startTime, endTime, effectiveUserId],
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ const UsagePanel: React.FC<Props> = ({ accessToken, userId }) => {
|
|||
startTime: start,
|
||||
endTime: end,
|
||||
entityIds: userId ? [userId] : null,
|
||||
includeCurrentUtcDay: true,
|
||||
};
|
||||
return dailyActivityAggregatedCall("user", request);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
dailyActivityExportCall,
|
||||
dailyActivityKeySearchCall,
|
||||
dailyActivityModelTopKeysCall,
|
||||
gatewayDailyActivityCall,
|
||||
} from "./networking";
|
||||
import type { DailyActivityEntity, DailyActivityRequest } from "./UsagePage/dailyActivityApi";
|
||||
|
||||
|
|
@ -26,8 +27,8 @@ const captureFetch = () => {
|
|||
const requestedUrl = (mockFetch: ReturnType<typeof captureFetch>, callIndex = 0): URL =>
|
||||
new URL(String(mockFetch.mock.calls[callIndex][0]), "http://example.com");
|
||||
|
||||
const start = new Date("2025-01-05T00:00:00Z");
|
||||
const end = new Date("2025-01-31T00:00:00Z");
|
||||
const start = new Date(2025, 0, 5);
|
||||
const end = new Date(2025, 0, 31);
|
||||
|
||||
const req = (overrides: Partial<DailyActivityRequest> = {}): DailyActivityRequest => ({
|
||||
accessToken: "sk-key",
|
||||
|
|
@ -38,6 +39,8 @@ const req = (overrides: Partial<DailyActivityRequest> = {}): DailyActivityReques
|
|||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
describe("dailyActivityAggregatedCall", () => {
|
||||
|
|
@ -115,13 +118,20 @@ describe("dailyActivityAggregatedCall", () => {
|
|||
expect(requestedUrl(mockFetch).searchParams.has("api_key")).toBe(false);
|
||||
});
|
||||
|
||||
it("sends include_current_utc_day only when set", async () => {
|
||||
const mockFetch = captureFetch();
|
||||
it.each<[DailyActivityEntity]>([["user"], ["team"], ["tag"], ["organization"], ["customer"], ["agent"]])(
|
||||
"preserves true, false, and omitted live-day settings for %s",
|
||||
async (entity) => {
|
||||
const mockFetch = captureFetch();
|
||||
|
||||
await dailyActivityAggregatedCall("user", req({ includeCurrentUtcDay: true }));
|
||||
for (const setting of [true, false, undefined]) {
|
||||
await dailyActivityAggregatedCall(entity, req({ includeCurrentUtcDay: setting }));
|
||||
|
||||
expect(requestedUrl(mockFetch).searchParams.get("include_current_utc_day")).toBe("true");
|
||||
});
|
||||
expect(
|
||||
requestedUrl(mockFetch, mockFetch.mock.calls.length - 1).searchParams.get("include_current_utc_day"),
|
||||
).toBe(setting === undefined ? null : String(setting));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("sends api_key_limit only when provided", async () => {
|
||||
const mockFetch = captureFetch();
|
||||
|
|
@ -134,6 +144,29 @@ describe("dailyActivityAggregatedCall", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("gatewayDailyActivityCall", () => {
|
||||
it.each(["America/Los_Angeles", "Asia/Kolkata"])(
|
||||
"sends local date boundaries and the caller's live-day timezone in %s",
|
||||
async (timezone) => {
|
||||
vi.stubEnv("TZ", timezone);
|
||||
vi.useFakeTimers({ toFake: ["Date"] });
|
||||
vi.setSystemTime(new Date("2026-08-06T02:00:00Z"));
|
||||
const mockFetch = captureFetch();
|
||||
const rangeStart = new Date(2026, 11, 31);
|
||||
const rangeEnd = new Date(2027, 0, 1);
|
||||
|
||||
await gatewayDailyActivityCall("sk-key", rangeStart, rangeEnd);
|
||||
|
||||
const url = requestedUrl(mockFetch);
|
||||
expect(url.pathname).toBe("/gateway/daily/activity");
|
||||
expect(url.searchParams.get("start_date")).toBe("2026-12-31");
|
||||
expect(url.searchParams.get("end_date")).toBe("2027-01-01");
|
||||
expect(url.searchParams.get("timezone")).toBe(String(new Date().getTimezoneOffset()));
|
||||
expect(url.searchParams.get("include_current_utc_day")).toBe("true");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("dailyActivityKeySearchCall", () => {
|
||||
it("GETs the search route with the search term", async () => {
|
||||
const mockFetch = captureFetch();
|
||||
|
|
|
|||
|
|
@ -1384,7 +1384,7 @@ const dailyActivityQuery = (
|
|||
? { [excludeParam]: excludeEntityIds && excludeEntityIds.length > 0 ? excludeEntityIds.join(",") : undefined }
|
||||
: {}),
|
||||
timezone: new Date().getTimezoneOffset().toString(),
|
||||
include_current_utc_day: entity === "user" && req.includeCurrentUtcDay ? "true" : undefined,
|
||||
include_current_utc_day: req.includeCurrentUtcDay?.toString(),
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
|
|
@ -2270,6 +2270,8 @@ export const gatewayDailyActivityCall = async (accessToken: string, startTime: D
|
|||
query: {
|
||||
start_date: formatDate(startTime),
|
||||
end_date: formatDate(endTime),
|
||||
timezone: new Date().getTimezoneOffset().toString(),
|
||||
include_current_utc_day: "true",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
|||
94
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
94
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -53965,7 +53965,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_agent_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -54004,7 +54007,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_agent_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -54044,7 +54050,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_agent_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -54083,7 +54092,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_agent_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -54122,7 +54134,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_agent_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -59652,7 +59667,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -59691,7 +59709,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -59731,7 +59752,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -59770,7 +59794,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -59809,7 +59836,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -60703,7 +60733,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -60742,7 +60775,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -60782,7 +60818,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -60821,7 +60860,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -60860,7 +60902,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_end_user_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -62474,6 +62519,10 @@ export interface operations {
|
|||
start_date?: string | null;
|
||||
/** @description End date in YYYY-MM-DD format */
|
||||
end_date?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC, matching JavaScript's Date.getTimezoneOffset(). */
|
||||
timezone?: number | null;
|
||||
/** @description Include the current UTC bucket when the range reaches the caller's local today. Requires timezone. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -70063,7 +70112,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_organization_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -70102,7 +70154,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_organization_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -70142,7 +70197,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_organization_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -70181,7 +70239,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_organization_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -70220,7 +70281,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_organization_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -76334,7 +76398,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
tags?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -76372,7 +76439,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
tags?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -76411,7 +76481,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
tags?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -76449,7 +76522,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
tags?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -76487,7 +76563,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
tags?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -77025,7 +77104,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_team_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -77064,7 +77146,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_team_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -77104,7 +77189,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_team_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -77143,7 +77231,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_team_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
@ -77182,7 +77273,10 @@ export interface operations {
|
|||
model?: string | null;
|
||||
api_key?: string | null;
|
||||
exclude_team_ids?: string | null;
|
||||
/** @description Timezone offset in minutes from UTC (e.g., 480 for PST). Matches JavaScript's Date.getTimezoneOffset() convention. */
|
||||
timezone?: number | null;
|
||||
/** @description When the range ends on the caller's current local day, extend it to today's UTC bucket so spend written after the caller's local midnight (in UTC terms) is included. Requires the timezone parameter. Historical ranges are never extended. */
|
||||
include_current_utc_day?: boolean;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue