mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-03 04:22:22 +00:00
Merge pull request #12042 from colesmcintosh/fix-today-selector-date-mutation-bug
Fix today selector date mutation bug in dashboard components
This commit is contained in:
Generated
+1
@@ -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"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
Icon,
|
||||
Text,
|
||||
} from "@tremor/react";
|
||||
import UsageDatePicker from "./shared/usage_date_picker";
|
||||
|
||||
import {
|
||||
Button as Button2,
|
||||
@@ -161,12 +162,6 @@ const CacheDashboard: React.FC<CachePageProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// the endTime put it to the last hour of the selected date
|
||||
endTime.setHours(23, 59, 59, 999);
|
||||
|
||||
// startTime put it to the first hour of the selected date
|
||||
startTime.setHours(0, 0, 0, 0);
|
||||
|
||||
let new_cache_data = await adminGlobalCacheActivity(
|
||||
accessToken,
|
||||
formatDateWithoutTZ(startTime),
|
||||
@@ -349,14 +344,12 @@ const runCachingHealthCheck = async () => {
|
||||
</MultiSelect>
|
||||
</Col>
|
||||
<Col>
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
<UsageDatePicker
|
||||
value={dateValue}
|
||||
onValueChange={(value) => {
|
||||
setDateValue(value);
|
||||
updateCachingData(value.from, value.to);
|
||||
}}
|
||||
selectPlaceholder="Select date range"
|
||||
/>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
||||
@@ -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';
|
||||
@@ -94,8 +95,9 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
|
||||
|
||||
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(
|
||||
@@ -287,9 +289,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
|
||||
<div style={{ width: "100%" }}>
|
||||
<Grid numItems={2} className="gap-2 w-full mb-4">
|
||||
<Col>
|
||||
<Text>Select Time Range</Text>
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
<UsageDatePicker
|
||||
value={dateValue}
|
||||
onValueChange={setDateValue}
|
||||
/>
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
MultiSelectItem,
|
||||
DateRangePickerValue,
|
||||
} from "@tremor/react";
|
||||
import UsageDatePicker from "./shared/usage_date_picker";
|
||||
import {
|
||||
modelInfoCall,
|
||||
userGetRequesedtModelsCall,
|
||||
@@ -306,15 +307,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
selected_customer = null;
|
||||
}
|
||||
|
||||
// make startTime and endTime to last hour of the day
|
||||
startTime.setHours(0);
|
||||
startTime.setMinutes(0);
|
||||
startTime.setSeconds(0);
|
||||
|
||||
endTime.setHours(23);
|
||||
endTime.setMinutes(59);
|
||||
endTime.setSeconds(59);
|
||||
|
||||
try {
|
||||
const modelMetricsResponse = await modelMetricsCall(
|
||||
accessToken,
|
||||
@@ -1387,9 +1379,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
<TabPanel>
|
||||
<Grid numItems={4} className="mt-2 mb-2">
|
||||
<Col>
|
||||
<Text>Select Time Range</Text>
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
<UsageDatePicker
|
||||
value={dateValue}
|
||||
className="mr-2"
|
||||
onValueChange={(value) => {
|
||||
@@ -1398,7 +1388,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
selectedModelGroup,
|
||||
value.from,
|
||||
value.to
|
||||
); // Call updateModelMetrics with the new date range
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
@@ -211,8 +212,9 @@ const NewUsagePage: React.FC<NewUsagePageProps> = ({
|
||||
|
||||
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
|
||||
@@ -281,9 +283,7 @@ const NewUsagePage: React.FC<NewUsagePageProps> = ({
|
||||
<TabPanel>
|
||||
<Grid numItems={2} className="gap-2 w-full mb-4">
|
||||
<Col>
|
||||
<Text>Select Time Range</Text>
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
<UsageDatePicker
|
||||
value={dateValue}
|
||||
onValueChange={(value) => {
|
||||
setDateValue(value);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import React from "react";
|
||||
import { DateRangePicker, DateRangePickerValue, Text } from "@tremor/react";
|
||||
|
||||
interface UsageDatePickerProps {
|
||||
value: DateRangePickerValue;
|
||||
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<UsageDatePickerProps> = ({
|
||||
value,
|
||||
onValueChange,
|
||||
label = "Select Time Range",
|
||||
className = "",
|
||||
showTimeRange = true
|
||||
}) => {
|
||||
const handleDateChange = (newValue: DateRangePickerValue) => {
|
||||
// Handle the case where "Today" or same-day selection is made
|
||||
if (newValue.from) {
|
||||
const adjustedValue = { ...newValue };
|
||||
|
||||
// Create new Date objects to avoid mutating the original dates
|
||||
const adjustedStartTime = new Date(newValue.from);
|
||||
let adjustedEndTime: Date;
|
||||
|
||||
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, 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
|
||||
} 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 no from date, pass through as-is
|
||||
onValueChange(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className={className}>
|
||||
{label && <Text className="mb-2">{label}</Text>}
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
value={value}
|
||||
onValueChange={handleDateChange}
|
||||
/>
|
||||
{showTimeRange && value.from && value.to && (
|
||||
<Text className="mt-1 text-xs text-gray-500">
|
||||
{formatTimeRange(value.from, value.to)}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsageDatePicker;
|
||||
@@ -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,12 +214,6 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// the endTime put it to the last hour of the selected date
|
||||
endTime.setHours(23, 59, 59, 999);
|
||||
|
||||
// startTime put it to the first hour of the selected date
|
||||
startTime.setHours(0, 0, 0, 0);
|
||||
|
||||
console.log("uiSelectedKey", uiSelectedKey);
|
||||
|
||||
let newTopUserData = await adminTopEndUsersCall(
|
||||
@@ -245,12 +240,6 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||
return; // Don't run expensive DB queries - return out when SpendLogs has more than 1M rows
|
||||
}
|
||||
|
||||
// the endTime put it to the last hour of the selected date
|
||||
endTime.setHours(23, 59, 59, 999);
|
||||
|
||||
// startTime put it to the first hour of the selected date
|
||||
startTime.setHours(0, 0, 0, 0);
|
||||
|
||||
let top_tags = await tagsSpendLogsCall(
|
||||
accessToken,
|
||||
startTime.toISOString(),
|
||||
@@ -835,14 +824,11 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||
<p className="mb-2 text-gray-500 italic text-[12px]">Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls <a className="text-blue-500" href="https://docs.litellm.ai/docs/proxy/users" target="_blank">docs here</a></p>
|
||||
<Grid numItems={2}>
|
||||
<Col>
|
||||
<Text>Select Time Range</Text>
|
||||
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
value={dateValue}
|
||||
<UsageDatePicker
|
||||
value={dateValue}
|
||||
onValueChange={(value) => {
|
||||
setDateValue(value);
|
||||
updateEndUserData(value.from, value.to, null); // Call updateModelMetrics with the new date range
|
||||
updateEndUserData(value.from, value.to, null);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
@@ -916,13 +902,12 @@ const UsagePage: React.FC<UsagePageProps> = ({
|
||||
<TabPanel>
|
||||
<Grid numItems={2}>
|
||||
<Col numColSpan={1}>
|
||||
<DateRangePicker
|
||||
<UsageDatePicker
|
||||
className="mb-4"
|
||||
enableSelect={true}
|
||||
value={dateValue}
|
||||
value={dateValue}
|
||||
onValueChange={(value) => {
|
||||
setDateValue(value);
|
||||
updateTagSpendData(value.from, value.to); // Call updateModelMetrics with the new date range
|
||||
updateTagSpendData(value.from, value.to);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user