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:
Cole McIntosh
2025-06-26 14:43:02 -06:00
committed by GitHub
8 changed files with 172 additions and 61 deletions
+1
View File
@@ -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;
+7 -22
View File
@@ -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);
}}
/>