From c42a876735e4a448ba3641d1742092326705db9b Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Tue, 24 Jun 2025 14:28:52 -0600 Subject: [PATCH 1/7] Fix date mutation bug causing 'Today' selector to return no data - Create new Date objects instead of mutating original DateRangePicker dates - Fixes issue where first API call would mutate dateValue.from/to causing subsequent calls to use corrupted date ranges - Updated usage.tsx, cache_dashboard.tsx, and model_dashboard.tsx - Minimal change preserving original timezone logic --- .../src/components/cache_dashboard.tsx | 12 ++++-- .../src/components/model_dashboard.tsx | 42 ++++++++++--------- ui/litellm-dashboard/src/components/usage.tsx | 24 +++++++---- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/ui/litellm-dashboard/src/components/cache_dashboard.tsx b/ui/litellm-dashboard/src/components/cache_dashboard.tsx index 37875cb7f9..2bb6b28af0 100644 --- a/ui/litellm-dashboard/src/components/cache_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/cache_dashboard.tsx @@ -161,16 +161,20 @@ const CacheDashboard: React.FC = ({ return; } + // Create new Date objects to avoid mutating the original dates + const adjustedStartTime = new Date(startTime); + const adjustedEndTime = new Date(endTime); + // the endTime put it to the last hour of the selected date - endTime.setHours(23, 59, 59, 999); + adjustedEndTime.setHours(23, 59, 59, 999); // startTime put it to the first hour of the selected date - startTime.setHours(0, 0, 0, 0); + adjustedStartTime.setHours(0, 0, 0, 0); let new_cache_data = await adminGlobalCacheActivity( accessToken, - formatDateWithoutTZ(startTime), - formatDateWithoutTZ(endTime) + formatDateWithoutTZ(adjustedStartTime), + formatDateWithoutTZ(adjustedEndTime) ) setData(new_cache_data); diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index 61f9353bb0..c40092c3f4 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -306,14 +306,18 @@ const ModelDashboard: React.FC = ({ selected_customer = null; } - // make startTime and endTime to last hour of the day - startTime.setHours(0); - startTime.setMinutes(0); - startTime.setSeconds(0); + // Create new Date objects to avoid mutating the original dates + const adjustedStartTime = new Date(startTime); + const adjustedEndTime = new Date(endTime); - endTime.setHours(23); - endTime.setMinutes(59); - endTime.setSeconds(59); + // make startTime and endTime to last hour of the day + adjustedStartTime.setHours(0); + adjustedStartTime.setMinutes(0); + adjustedStartTime.setSeconds(0); + + adjustedEndTime.setHours(23); + adjustedEndTime.setMinutes(59); + adjustedEndTime.setSeconds(59); try { const modelMetricsResponse = await modelMetricsCall( @@ -321,8 +325,8 @@ const ModelDashboard: React.FC = ({ userID, userRole, modelGroup, - startTime.toISOString(), - endTime.toISOString(), + adjustedStartTime.toISOString(), + adjustedEndTime.toISOString(), selected_token, selected_customer ); @@ -335,8 +339,8 @@ const ModelDashboard: React.FC = ({ const streamingModelMetricsResponse = await streamingModelMetricsCall( accessToken, modelGroup, - startTime.toISOString(), - endTime.toISOString() + adjustedStartTime.toISOString(), + adjustedEndTime.toISOString() ); // Assuming modelMetricsResponse now contains the metric data for the specified model group @@ -350,8 +354,8 @@ const ModelDashboard: React.FC = ({ userID, userRole, modelGroup, - startTime.toISOString(), - endTime.toISOString(), + adjustedStartTime.toISOString(), + adjustedEndTime.toISOString(), selected_token, selected_customer ); @@ -364,8 +368,8 @@ const ModelDashboard: React.FC = ({ userID, userRole, modelGroup, - startTime.toISOString(), - endTime.toISOString(), + adjustedStartTime.toISOString(), + adjustedEndTime.toISOString(), selected_token, selected_customer ); @@ -378,8 +382,8 @@ const ModelDashboard: React.FC = ({ if (modelGroup) { const dailyExceptions = await adminGlobalActivityExceptions( accessToken, - startTime?.toISOString().split('T')[0], - endTime?.toISOString().split('T')[0], + adjustedStartTime?.toISOString().split('T')[0], + adjustedEndTime?.toISOString().split('T')[0], modelGroup, ); @@ -387,8 +391,8 @@ const ModelDashboard: React.FC = ({ const dailyExceptionsPerDeplyment = await adminGlobalActivityExceptionsPerDeployment( accessToken, - startTime?.toISOString().split('T')[0], - endTime?.toISOString().split('T')[0], + adjustedStartTime?.toISOString().split('T')[0], + adjustedEndTime?.toISOString().split('T')[0], modelGroup, ) diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 6779ba7f94..d1c0ecb87e 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -213,19 +213,23 @@ const UsagePage: React.FC = ({ return; } + // Create new Date objects to avoid mutating the original dates + const adjustedStartTime = new Date(startTime); + const adjustedEndTime = new Date(endTime); + // the endTime put it to the last hour of the selected date - endTime.setHours(23, 59, 59, 999); + adjustedEndTime.setHours(23, 59, 59, 999); // startTime put it to the first hour of the selected date - startTime.setHours(0, 0, 0, 0); + adjustedStartTime.setHours(0, 0, 0, 0); console.log("uiSelectedKey", uiSelectedKey); let newTopUserData = await adminTopEndUsersCall( accessToken, uiSelectedKey, - startTime.toISOString(), - endTime.toISOString() + adjustedStartTime.toISOString(), + adjustedEndTime.toISOString() ) console.log("End user data updated successfully", newTopUserData); setTopUsers(newTopUserData); @@ -245,16 +249,20 @@ const UsagePage: React.FC = ({ return; // Don't run expensive DB queries - return out when SpendLogs has more than 1M rows } + // Create new Date objects to avoid mutating the original dates + const adjustedStartTime = new Date(startTime); + const adjustedEndTime = new Date(endTime); + // the endTime put it to the last hour of the selected date - endTime.setHours(23, 59, 59, 999); + adjustedEndTime.setHours(23, 59, 59, 999); // startTime put it to the first hour of the selected date - startTime.setHours(0, 0, 0, 0); + adjustedStartTime.setHours(0, 0, 0, 0); let top_tags = await tagsSpendLogsCall( accessToken, - startTime.toISOString(), - endTime.toISOString(), + adjustedStartTime.toISOString(), + adjustedEndTime.toISOString(), selectedTags.length === 0 ? undefined : selectedTags ); setTopTagsData(top_tags.spend_per_tag); From ca96e3922964bd20182b8159196a2258b0de5239 Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Tue, 24 Jun 2025 14:29:14 -0600 Subject: [PATCH 2/7] Update package-lock.json to include @anthropic-ai/sdk version 0.54.0 --- ui/litellm-dashboard/package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index aa8402dba6..9e40e73d2b 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -143,6 +143,7 @@ "version": "0.54.0", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.54.0.tgz", "integrity": "sha512-xyoCtHJnt/qg5GG6IgK+UJEndz8h8ljzt/caKXmq3LfBF81nC/BW6E4x2rOWCZcvsLyVW+e8U5mtIr6UCE/kJw==", + "license": "MIT", "bin": { "anthropic-ai-sdk": "bin/cli" } From 52e51ead0365094e6dadff21a8d21cb00f713df5 Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Wed, 25 Jun 2025 11:14:48 -0600 Subject: [PATCH 3/7] Add UsageDatePicker component for usage dashboards --- .../components/shared/usage_date_picker.tsx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx diff --git a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx new file mode 100644 index 0000000000..039cee4fc0 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { DateRangePicker, DateRangePickerValue, Text } from "@tremor/react"; + +interface UsageDatePickerProps { + value: DateRangePickerValue; + onValueChange: (value: DateRangePickerValue) => void; + label?: string; + className?: string; +} + +/** + * Reusable date picker component for usage dashboards. + * Handles proper time boundaries for date ranges, especially "Today" selections. + */ +const UsageDatePicker: React.FC = ({ + value, + onValueChange, + label = "Select Time Range", + className = "" +}) => { + const handleDateChange = (newValue: DateRangePickerValue) => { + // Handle the case where "Today" or same-day selection is made + if (newValue.from && newValue.to) { + const adjustedValue = { ...newValue }; + + // Create new Date objects to avoid mutating the original dates + const adjustedStartTime = new Date(newValue.from); + const adjustedEndTime = new Date(newValue.to); + + // Check if it's the same day (Today selection) + const isSameDay = + adjustedStartTime.toDateString() === adjustedEndTime.toDateString(); + + if (isSameDay) { + // For same-day selections (like "Today"), set proper time boundaries + adjustedStartTime.setHours(0, 0, 0, 0); // Start of day + adjustedEndTime.setHours(23, 59, 59, 999); // End of day + } else { + // For multi-day ranges, set start to beginning of first day and end to end of last day + adjustedStartTime.setHours(0, 0, 0, 0); + adjustedEndTime.setHours(23, 59, 59, 999); + } + + adjustedValue.from = adjustedStartTime; + adjustedValue.to = adjustedEndTime; + + onValueChange(adjustedValue); + } else { + // If either date is missing, pass through as-is + onValueChange(newValue); + } + }; + + return ( +
+ {label && {label}} + +
+ ); +}; + +export default UsageDatePicker; \ No newline at end of file From 3f644602fbbc3b24b334bfa8be100900a9b6bf7b Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Wed, 25 Jun 2025 11:16:01 -0600 Subject: [PATCH 4/7] Refactor date range selection in dashboards to use UsageDatePicker component, simplifying date handling logic and improving consistency across components. --- .../src/components/cache_dashboard.tsx | 19 ++------ .../src/components/entity_usage.tsx | 5 +-- .../src/components/model_dashboard.tsx | 24 +++------- .../src/components/new_usage.tsx | 5 +-- ui/litellm-dashboard/src/components/usage.tsx | 45 +++++-------------- 5 files changed, 24 insertions(+), 74 deletions(-) diff --git a/ui/litellm-dashboard/src/components/cache_dashboard.tsx b/ui/litellm-dashboard/src/components/cache_dashboard.tsx index 2bb6b28af0..2db37622d9 100644 --- a/ui/litellm-dashboard/src/components/cache_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/cache_dashboard.tsx @@ -22,6 +22,7 @@ import { Icon, Text, } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { Button as Button2, @@ -161,20 +162,10 @@ const CacheDashboard: React.FC = ({ return; } - // Create new Date objects to avoid mutating the original dates - const adjustedStartTime = new Date(startTime); - const adjustedEndTime = new Date(endTime); - - // the endTime put it to the last hour of the selected date - adjustedEndTime.setHours(23, 59, 59, 999); - - // startTime put it to the first hour of the selected date - adjustedStartTime.setHours(0, 0, 0, 0); - let new_cache_data = await adminGlobalCacheActivity( accessToken, - formatDateWithoutTZ(adjustedStartTime), - formatDateWithoutTZ(adjustedEndTime) + formatDateWithoutTZ(startTime), + formatDateWithoutTZ(endTime) ) setData(new_cache_data); @@ -353,14 +344,12 @@ const runCachingHealthCheck = async () => { - { setDateValue(value); updateCachingData(value.from, value.to); }} - selectPlaceholder="Select date range" /> diff --git a/ui/litellm-dashboard/src/components/entity_usage.tsx b/ui/litellm-dashboard/src/components/entity_usage.tsx index ade70abaab..10af11b009 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.tsx @@ -6,6 +6,7 @@ import { DonutChart, TabPanel, TabGroup, TabList, Tab, TabPanels } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { Select } from 'antd'; import { ActivityMetrics, processActivityData } from './activity_metrics'; import { DailyData, KeyMetricWithMetadata, EntityMetricWithMetadata } from './usage/types'; @@ -287,9 +288,7 @@ const EntityUsage: React.FC = ({
- Select Time Range - diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index c40092c3f4..cc50e5ac71 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -43,6 +43,7 @@ import { MultiSelectItem, DateRangePickerValue, } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { modelInfoCall, userGetRequesedtModelsCall, @@ -306,27 +307,14 @@ const ModelDashboard: React.FC = ({ selected_customer = null; } - // Create new Date objects to avoid mutating the original dates - const adjustedStartTime = new Date(startTime); - const adjustedEndTime = new Date(endTime); - - // make startTime and endTime to last hour of the day - adjustedStartTime.setHours(0); - adjustedStartTime.setMinutes(0); - adjustedStartTime.setSeconds(0); - - adjustedEndTime.setHours(23); - adjustedEndTime.setMinutes(59); - adjustedEndTime.setSeconds(59); - try { const modelMetricsResponse = await modelMetricsCall( accessToken, userID, userRole, modelGroup, - adjustedStartTime.toISOString(), - adjustedEndTime.toISOString(), + startTime.toISOString(), + endTime.toISOString(), selected_token, selected_customer ); @@ -1391,9 +1379,7 @@ const ModelDashboard: React.FC = ({ - Select Time Range - { @@ -1402,7 +1388,7 @@ const ModelDashboard: React.FC = ({ selectedModelGroup, value.from, value.to - ); // Call updateModelMetrics with the new date range + ); }} /> diff --git a/ui/litellm-dashboard/src/components/new_usage.tsx b/ui/litellm-dashboard/src/components/new_usage.tsx index 0040937245..da967894ee 100644 --- a/ui/litellm-dashboard/src/components/new_usage.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.tsx @@ -15,6 +15,7 @@ import { TableHeaderCell, TableBody, TableCell, Subtitle, DateRangePicker, DateRangePickerValue } from "@tremor/react"; +import UsageDatePicker from "./shared/usage_date_picker"; import { AreaChart } from "@tremor/react"; import { userDailyActivityCall, tagListCall } from "./networking"; @@ -273,9 +274,7 @@ const NewUsagePage: React.FC = ({ - Select Time Range - { setDateValue(value); diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index d1c0ecb87e..74a411fcbc 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from "react"; import ViewUserSpend from "./view_user_spend"; import { ProxySettings } from "./user_dashboard"; +import UsageDatePicker from "./shared/usage_date_picker"; import { Grid, Col, Text, LineChart, TabPanel, TabPanels, @@ -213,23 +214,13 @@ const UsagePage: React.FC = ({ return; } - // Create new Date objects to avoid mutating the original dates - const adjustedStartTime = new Date(startTime); - const adjustedEndTime = new Date(endTime); - - // the endTime put it to the last hour of the selected date - adjustedEndTime.setHours(23, 59, 59, 999); - - // startTime put it to the first hour of the selected date - adjustedStartTime.setHours(0, 0, 0, 0); - console.log("uiSelectedKey", uiSelectedKey); let newTopUserData = await adminTopEndUsersCall( accessToken, uiSelectedKey, - adjustedStartTime.toISOString(), - adjustedEndTime.toISOString() + startTime.toISOString(), + endTime.toISOString() ) console.log("End user data updated successfully", newTopUserData); setTopUsers(newTopUserData); @@ -249,20 +240,10 @@ const UsagePage: React.FC = ({ return; // Don't run expensive DB queries - return out when SpendLogs has more than 1M rows } - // Create new Date objects to avoid mutating the original dates - const adjustedStartTime = new Date(startTime); - const adjustedEndTime = new Date(endTime); - - // the endTime put it to the last hour of the selected date - adjustedEndTime.setHours(23, 59, 59, 999); - - // startTime put it to the first hour of the selected date - adjustedStartTime.setHours(0, 0, 0, 0); - let top_tags = await tagsSpendLogsCall( accessToken, - adjustedStartTime.toISOString(), - adjustedEndTime.toISOString(), + startTime.toISOString(), + endTime.toISOString(), selectedTags.length === 0 ? undefined : selectedTags ); setTopTagsData(top_tags.spend_per_tag); @@ -843,14 +824,11 @@ const UsagePage: React.FC = ({

Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls docs here

- Select Time Range - - { setDateValue(value); - updateEndUserData(value.from, value.to, null); // Call updateModelMetrics with the new date range + updateEndUserData(value.from, value.to, null); }} /> @@ -924,13 +902,12 @@ const UsagePage: React.FC = ({ - { setDateValue(value); - updateTagSpendData(value.from, value.to); // Call updateModelMetrics with the new date range + updateTagSpendData(value.from, value.to); }} /> From 102680d0ed53f461d35e8137ef5c64561121f86a Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Wed, 25 Jun 2025 11:28:24 -0600 Subject: [PATCH 5/7] Refactor date handling in ModelDashboard to use startTime and endTime directly, improving clarity and consistency in metric data retrieval. --- .../src/components/model_dashboard.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index cc50e5ac71..4ae965484a 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -327,8 +327,8 @@ const ModelDashboard: React.FC = ({ const streamingModelMetricsResponse = await streamingModelMetricsCall( accessToken, modelGroup, - adjustedStartTime.toISOString(), - adjustedEndTime.toISOString() + startTime.toISOString(), + endTime.toISOString() ); // Assuming modelMetricsResponse now contains the metric data for the specified model group @@ -342,8 +342,8 @@ const ModelDashboard: React.FC = ({ userID, userRole, modelGroup, - adjustedStartTime.toISOString(), - adjustedEndTime.toISOString(), + startTime.toISOString(), + endTime.toISOString(), selected_token, selected_customer ); @@ -356,8 +356,8 @@ const ModelDashboard: React.FC = ({ userID, userRole, modelGroup, - adjustedStartTime.toISOString(), - adjustedEndTime.toISOString(), + startTime.toISOString(), + endTime.toISOString(), selected_token, selected_customer ); @@ -370,8 +370,8 @@ const ModelDashboard: React.FC = ({ if (modelGroup) { const dailyExceptions = await adminGlobalActivityExceptions( accessToken, - adjustedStartTime?.toISOString().split('T')[0], - adjustedEndTime?.toISOString().split('T')[0], + startTime?.toISOString().split('T')[0], + endTime?.toISOString().split('T')[0], modelGroup, ); @@ -379,8 +379,8 @@ const ModelDashboard: React.FC = ({ const dailyExceptionsPerDeplyment = await adminGlobalActivityExceptionsPerDeployment( accessToken, - adjustedStartTime?.toISOString().split('T')[0], - adjustedEndTime?.toISOString().split('T')[0], + startTime?.toISOString().split('T')[0], + endTime?.toISOString().split('T')[0], modelGroup, ) From cdf95f5f2ca819d9c36a8f990c2976aa22b69d5b Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Wed, 25 Jun 2025 11:28:35 -0600 Subject: [PATCH 6/7] Enhance UsageDatePicker component by adding time range display and improving timezone handling for date selections. Default time range visibility is set to true, ensuring better user experience in usage dashboards. --- .../components/shared/usage_date_picker.tsx | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx index 039cee4fc0..82f8aa2441 100644 --- a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx +++ b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx @@ -6,17 +6,20 @@ interface UsageDatePickerProps { onValueChange: (value: DateRangePickerValue) => void; label?: string; className?: string; + showTimeRange?: boolean; } /** * Reusable date picker component for usage dashboards. * Handles proper time boundaries for date ranges, especially "Today" selections. + * Addresses timezone issues by ensuring UTC time boundaries are set correctly. */ const UsageDatePicker: React.FC = ({ value, onValueChange, label = "Select Time Range", - className = "" + className = "", + showTimeRange = true }) => { const handleDateChange = (newValue: DateRangePickerValue) => { // Handle the case where "Today" or same-day selection is made @@ -33,8 +36,9 @@ const UsageDatePicker: React.FC = ({ if (isSameDay) { // For same-day selections (like "Today"), set proper time boundaries - adjustedStartTime.setHours(0, 0, 0, 0); // Start of day - adjustedEndTime.setHours(23, 59, 59, 999); // End of day + // Use local timezone boundaries that will be converted to UTC properly + adjustedStartTime.setHours(0, 0, 0, 0); // Start of day in local time + adjustedEndTime.setHours(23, 59, 59, 999); // End of day in local time } else { // For multi-day ranges, set start to beginning of first day and end to end of last day adjustedStartTime.setHours(0, 0, 0, 0); @@ -51,6 +55,45 @@ const UsageDatePicker: React.FC = ({ } }; + const formatTimeRange = (from: Date | undefined, to: Date | undefined) => { + if (!from || !to) return ""; + + const formatDateTime = (date: Date) => { + return date.toLocaleString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZoneName: 'short' + }); + }; + + const isSameDay = from.toDateString() === to.toDateString(); + + if (isSameDay) { + const dateStr = from.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); + const startTime = from.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true + }); + const endTime = to.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZoneName: 'short' + }); + return `${dateStr}: ${startTime} - ${endTime}`; + } else { + return `${formatDateTime(from)} - ${formatDateTime(to)}`; + } + }; + return (
{label && {label}} @@ -59,6 +102,11 @@ const UsageDatePicker: React.FC = ({ value={value} onValueChange={handleDateChange} /> + {showTimeRange && value.from && value.to && ( + + {formatTimeRange(value.from, value.to)} + + )}
); }; From 8f376a60fafc8f7dad15e447a8696c1c64e26c20 Mon Sep 17 00:00:00 2001 From: Cole McIntosh Date: Thu, 26 Jun 2025 13:59:40 -0600 Subject: [PATCH 7/7] fix(ui): fix Today filter not showing usage data in dashboard - Fix date formatting in API calls from ISO format to YYYY-MM-DD - Update userDailyActivityCall, teamDailyActivityCall, and tagDailyActivityCall - Prevent date mutation by creating new Date objects before API calls - Set proper time boundaries (00:00:00 to 23:59:59) for same-day selections The API expects dates in YYYY-MM-DD format but the UI was sending full ISO timestamps, causing the Today filter to return empty results. --- .../src/components/entity_usage.tsx | 5 +-- .../src/components/networking.tsx | 35 +++++++++++++++---- .../src/components/new_usage.tsx | 5 +-- .../components/shared/usage_date_picker.tsx | 17 ++++++--- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/ui/litellm-dashboard/src/components/entity_usage.tsx b/ui/litellm-dashboard/src/components/entity_usage.tsx index 10af11b009..a17bc66f7e 100644 --- a/ui/litellm-dashboard/src/components/entity_usage.tsx +++ b/ui/litellm-dashboard/src/components/entity_usage.tsx @@ -95,8 +95,9 @@ const EntityUsage: React.FC = ({ const fetchSpendData = async () => { if (!accessToken || !dateValue.from || !dateValue.to) return; - const startTime = dateValue.from; - const endTime = dateValue.to; + // Create new Date objects to avoid mutating the original dates + const startTime = new Date(dateValue.from); + const endTime = new Date(dateValue.to); if (entityType === 'tag') { const data = await tagDailyActivityCall( diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 2ec8642458..085fe5cfe9 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -19,7 +19,7 @@ import { } from "./email_events/types"; const isLocal = process.env.NODE_ENV === "development"; -export const defaultProxyBaseUrl = isLocal ? "http://localhost:4000" : null; +export const defaultProxyBaseUrl = isLocal ? "http://localhost:43845" : null; const defaultServerRootPath = "/"; export let serverRootPath = defaultServerRootPath; export let proxyBaseUrl = defaultProxyBaseUrl; @@ -1333,8 +1333,15 @@ export const userDailyActivityCall = async ( ? `${proxyBaseUrl}/user/daily/activity` : `/user/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append("start_date", startTime.toISOString()); - queryParams.append("end_date", endTime.toISOString()); + // Format dates as YYYY-MM-DD for the API + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + queryParams.append("start_date", formatDate(startTime)); + queryParams.append("end_date", formatDate(endTime)); queryParams.append("page_size", "1000"); queryParams.append("page", page.toString()); const queryString = queryParams.toString(); @@ -1379,8 +1386,15 @@ export const tagDailyActivityCall = async ( ? `${proxyBaseUrl}/tag/daily/activity` : `/tag/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append("start_date", startTime.toISOString()); - queryParams.append("end_date", endTime.toISOString()); + // Format dates as YYYY-MM-DD for the API + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + queryParams.append("start_date", formatDate(startTime)); + queryParams.append("end_date", formatDate(endTime)); queryParams.append("page_size", "1000"); queryParams.append("page", page.toString()); if (tags) { @@ -1428,8 +1442,15 @@ export const teamDailyActivityCall = async ( ? `${proxyBaseUrl}/team/daily/activity` : `/team/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append("start_date", startTime.toISOString()); - queryParams.append("end_date", endTime.toISOString()); + // Format dates as YYYY-MM-DD for the API + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + }; + queryParams.append("start_date", formatDate(startTime)); + queryParams.append("end_date", formatDate(endTime)); queryParams.append("page_size", "1000"); queryParams.append("page", page.toString()); if (teamIds) { diff --git a/ui/litellm-dashboard/src/components/new_usage.tsx b/ui/litellm-dashboard/src/components/new_usage.tsx index 294e041a63..3434f08c45 100644 --- a/ui/litellm-dashboard/src/components/new_usage.tsx +++ b/ui/litellm-dashboard/src/components/new_usage.tsx @@ -212,8 +212,9 @@ const NewUsagePage: React.FC = ({ const fetchUserSpendData = async () => { if (!accessToken || !dateValue.from || !dateValue.to) return; - const startTime = dateValue.from; - const endTime = dateValue.to; + // Create new Date objects to avoid mutating the original dates + const startTime = new Date(dateValue.from); + const endTime = new Date(dateValue.to); try { // Get first page diff --git a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx index 82f8aa2441..2d9bba75d5 100644 --- a/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx +++ b/ui/litellm-dashboard/src/components/shared/usage_date_picker.tsx @@ -23,19 +23,26 @@ const UsageDatePicker: React.FC = ({ }) => { const handleDateChange = (newValue: DateRangePickerValue) => { // Handle the case where "Today" or same-day selection is made - if (newValue.from && newValue.to) { + if (newValue.from) { const adjustedValue = { ...newValue }; // Create new Date objects to avoid mutating the original dates const adjustedStartTime = new Date(newValue.from); - const adjustedEndTime = new Date(newValue.to); + let adjustedEndTime: Date; - // Check if it's the same day (Today selection) + if (newValue.to) { + adjustedEndTime = new Date(newValue.to); + } else { + // If no end date is provided (like "Today" from dropdown), use the same date + adjustedEndTime = new Date(newValue.from); + } + + // Check if it's the same day (Today selection or single day selection) const isSameDay = adjustedStartTime.toDateString() === adjustedEndTime.toDateString(); if (isSameDay) { - // For same-day selections (like "Today"), set proper time boundaries + // For same-day selections, set proper time boundaries // Use local timezone boundaries that will be converted to UTC properly adjustedStartTime.setHours(0, 0, 0, 0); // Start of day in local time adjustedEndTime.setHours(23, 59, 59, 999); // End of day in local time @@ -50,7 +57,7 @@ const UsageDatePicker: React.FC = ({ onValueChange(adjustedValue); } else { - // If either date is missing, pass through as-is + // If no from date, pass through as-is onValueChange(newValue); } };