mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-07 23:08:07 +00:00
Merge pull request #23399 from BerriAI/revert-23174-feat_adding_timezone_to_usage_page
Revert "feat: adding a timezone picker to the usage page"
This commit is contained in:
+2
-9
@@ -85,10 +85,9 @@ interface EntityUsageProps {
|
||||
entityList: EntityList[] | null;
|
||||
premiumUser: boolean;
|
||||
dateValue: DateRangePickerValue;
|
||||
timezoneOffset?: number;
|
||||
}
|
||||
|
||||
const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, entityId, entityList, dateValue, timezoneOffset }) => {
|
||||
const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, entityId, entityList, dateValue }) => {
|
||||
const [spendData, setSpendData] = useState<EntitySpendData>({
|
||||
results: [],
|
||||
metadata: {
|
||||
@@ -120,7 +119,6 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
endTime,
|
||||
1,
|
||||
selectedTags.length > 0 ? selectedTags : null,
|
||||
timezoneOffset,
|
||||
);
|
||||
setSpendData(data);
|
||||
} else if (entityType === "team") {
|
||||
@@ -130,7 +128,6 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
endTime,
|
||||
1,
|
||||
selectedTags.length > 0 ? selectedTags : null,
|
||||
timezoneOffset,
|
||||
);
|
||||
setSpendData(data);
|
||||
} else if (entityType === "organization") {
|
||||
@@ -140,7 +137,6 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
endTime,
|
||||
1,
|
||||
selectedTags.length > 0 ? selectedTags : null,
|
||||
timezoneOffset,
|
||||
);
|
||||
setSpendData(data);
|
||||
} else if (entityType === "customer") {
|
||||
@@ -150,7 +146,6 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
endTime,
|
||||
1,
|
||||
selectedTags.length > 0 ? selectedTags : null,
|
||||
timezoneOffset,
|
||||
);
|
||||
setSpendData(data);
|
||||
} else if (entityType === "agent") {
|
||||
@@ -160,7 +155,6 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
endTime,
|
||||
1,
|
||||
selectedTags.length > 0 ? selectedTags : null,
|
||||
timezoneOffset,
|
||||
);
|
||||
setSpendData(data);
|
||||
} else if (entityType === "user") {
|
||||
@@ -170,7 +164,6 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
endTime,
|
||||
1,
|
||||
selectedTags.length > 0 ? selectedTags[0] : null,
|
||||
timezoneOffset,
|
||||
);
|
||||
setSpendData(data);
|
||||
} else {
|
||||
@@ -180,7 +173,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
||||
|
||||
useEffect(() => {
|
||||
fetchSpendData();
|
||||
}, [accessToken, dateValue, entityId, selectedTags, timezoneOffset]);
|
||||
}, [accessToken, dateValue, entityId, selectedTags]);
|
||||
|
||||
const getTopModels = () => {
|
||||
const modelSpend: { [key: string]: any } = {};
|
||||
|
||||
@@ -78,8 +78,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
to: initialToDate,
|
||||
});
|
||||
|
||||
const [timezoneOffset, setTimezoneOffset] = useState<number>(() => new Date().getTimezoneOffset());
|
||||
|
||||
const [allTags, setAllTags] = useState<EntityList[]>([]);
|
||||
const { data: customers = [] } = useCustomers();
|
||||
const { data: agentsResponse } = useAgents();
|
||||
@@ -378,14 +376,14 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
try {
|
||||
// Prefer aggregated endpoint to avoid many page requests
|
||||
try {
|
||||
const aggregated = await userDailyActivityAggregatedCall(accessToken, startTime, endTime, effectiveUserId, timezoneOffset);
|
||||
const aggregated = await userDailyActivityAggregatedCall(accessToken, startTime, endTime, effectiveUserId);
|
||||
setUserSpendData(aggregated);
|
||||
return;
|
||||
} catch (e) {
|
||||
// Fallback to paginated calls if aggregated endpoint is unavailable
|
||||
}
|
||||
|
||||
const firstPageData = await userDailyActivityCall(accessToken, startTime, endTime, 1, effectiveUserId, timezoneOffset);
|
||||
const firstPageData = await userDailyActivityCall(accessToken, startTime, endTime, 1, effectiveUserId);
|
||||
|
||||
if (firstPageData.metadata.total_pages <= 1) {
|
||||
setUserSpendData(firstPageData);
|
||||
@@ -396,7 +394,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
const aggregatedMetadata = { ...firstPageData.metadata };
|
||||
|
||||
for (let page = 2; page <= firstPageData.metadata.total_pages; page++) {
|
||||
const pageData = await userDailyActivityCall(accessToken, startTime, endTime, page, effectiveUserId, timezoneOffset);
|
||||
const pageData = await userDailyActivityCall(accessToken, startTime, endTime, page, effectiveUserId);
|
||||
allResults.push(...pageData.results);
|
||||
if (pageData.metadata) {
|
||||
aggregatedMetadata.total_spend += pageData.metadata.total_spend || 0;
|
||||
@@ -417,7 +415,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
setLoading(false);
|
||||
setIsDateChanging(false);
|
||||
}
|
||||
}, [accessToken, dateValue.from, dateValue.to, selectedUserId, isAdmin, userID, timezoneOffset]);
|
||||
}, [accessToken, dateValue.from, dateValue.to, selectedUserId, isAdmin, userID]);
|
||||
|
||||
// Super responsive date change handler
|
||||
const handleDateChange = useCallback((newValue: DateRangePickerValue) => {
|
||||
@@ -497,7 +495,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
onChange={(value) => setUsageView(value)}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
<AdvancedDatePicker value={dateValue} onValueChange={handleDateChange} timezoneOffset={timezoneOffset} onTimezoneChange={setTimezoneOffset} />
|
||||
<AdvancedDatePicker value={dateValue} onValueChange={handleDateChange} />
|
||||
</div>
|
||||
{/* Your Usage Panel */}
|
||||
{usageView === "global" && (
|
||||
@@ -827,7 +825,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
})) || null
|
||||
}
|
||||
premiumUser={premiumUser}
|
||||
timezoneOffset={timezoneOffset}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -846,7 +843,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
}
|
||||
premiumUser={premiumUser}
|
||||
dateValue={dateValue}
|
||||
timezoneOffset={timezoneOffset}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -865,7 +861,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
}
|
||||
premiumUser={premiumUser}
|
||||
dateValue={dateValue}
|
||||
timezoneOffset={timezoneOffset}
|
||||
/>
|
||||
)}
|
||||
{/* Tag Usage Panel */}
|
||||
@@ -896,7 +891,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
entityList={allTags}
|
||||
premiumUser={premiumUser}
|
||||
dateValue={dateValue}
|
||||
timezoneOffset={timezoneOffset}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -911,7 +905,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
}
|
||||
premiumUser={premiumUser}
|
||||
dateValue={dateValue}
|
||||
timezoneOffset={timezoneOffset}
|
||||
/>
|
||||
)}
|
||||
{/* User Usage Panel */}
|
||||
@@ -924,7 +917,6 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
||||
entityList={userOptions.length > 0 ? userOptions : null}
|
||||
premiumUser={premiumUser}
|
||||
dateValue={dateValue}
|
||||
timezoneOffset={timezoneOffset}
|
||||
/>
|
||||
)}
|
||||
{/* User Agent Activity Panel */}
|
||||
|
||||
@@ -1698,7 +1698,6 @@ const buildDailyActivityUrl = (
|
||||
endTime: Date,
|
||||
page: number,
|
||||
extraQueryParams?: Record<string, DailyActivityQueryValue>,
|
||||
timezoneOffset?: number,
|
||||
) => {
|
||||
const resolvedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
||||
const baseUrl = proxyBaseUrl ? `${proxyBaseUrl}${resolvedEndpoint}` : resolvedEndpoint;
|
||||
@@ -1709,8 +1708,7 @@ const buildDailyActivityUrl = (
|
||||
params.append("page_size", DEFAULT_DAILY_ACTIVITY_PAGE_SIZE);
|
||||
params.append("page", page.toString());
|
||||
// Send timezone offset so backend can adjust date range for UTC storage
|
||||
const tz = timezoneOffset !== undefined ? timezoneOffset : new Date().getTimezoneOffset();
|
||||
params.append("timezone", tz.toString());
|
||||
params.append("timezone", new Date().getTimezoneOffset().toString());
|
||||
|
||||
if (extraQueryParams) {
|
||||
Object.entries(extraQueryParams).forEach(([key, value]) => {
|
||||
@@ -1729,7 +1727,6 @@ type DailyActivityCallOptions = {
|
||||
endTime: Date;
|
||||
page?: number;
|
||||
extraQueryParams?: Record<string, DailyActivityQueryValue>;
|
||||
timezoneOffset?: number;
|
||||
};
|
||||
|
||||
const fetchDailyActivity = async ({
|
||||
@@ -1739,10 +1736,9 @@ const fetchDailyActivity = async ({
|
||||
endTime,
|
||||
page = 1,
|
||||
extraQueryParams,
|
||||
timezoneOffset,
|
||||
}: DailyActivityCallOptions) => {
|
||||
try {
|
||||
const url = buildDailyActivityUrl(endpoint, startTime, endTime, page, extraQueryParams, timezoneOffset);
|
||||
const url = buildDailyActivityUrl(endpoint, startTime, endTime, page, extraQueryParams);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
@@ -1767,7 +1763,7 @@ const fetchDailyActivity = async ({
|
||||
}
|
||||
};
|
||||
|
||||
export const userDailyActivityCall = async (accessToken: string, startTime: Date, endTime: Date, page: number = 1, userId: string | null = null, timezoneOffset?: number) => {
|
||||
export const userDailyActivityCall = async (accessToken: string, startTime: Date, endTime: Date, page: number = 1, userId: string | null = null) => {
|
||||
/**
|
||||
* Get daily user activity on proxy
|
||||
*/
|
||||
@@ -1780,7 +1776,6 @@ export const userDailyActivityCall = async (accessToken: string, startTime: Date
|
||||
extraQueryParams: {
|
||||
user_id: userId,
|
||||
},
|
||||
timezoneOffset,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1790,7 +1785,6 @@ export const tagDailyActivityCall = async (
|
||||
endTime: Date,
|
||||
page: number = 1,
|
||||
tags: string[] | null = null,
|
||||
timezoneOffset?: number,
|
||||
) => {
|
||||
/**
|
||||
* Get daily user activity on proxy
|
||||
@@ -1804,7 +1798,6 @@ export const tagDailyActivityCall = async (
|
||||
extraQueryParams: {
|
||||
tags,
|
||||
},
|
||||
timezoneOffset,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1814,7 +1807,6 @@ export const teamDailyActivityCall = async (
|
||||
endTime: Date,
|
||||
page: number = 1,
|
||||
teamIds: string[] | null = null,
|
||||
timezoneOffset?: number,
|
||||
) => {
|
||||
/**
|
||||
* Get daily user activity on proxy
|
||||
@@ -1829,7 +1821,6 @@ export const teamDailyActivityCall = async (
|
||||
team_ids: teamIds,
|
||||
exclude_team_ids: "litellm-dashboard",
|
||||
},
|
||||
timezoneOffset,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1839,7 +1830,6 @@ export const organizationDailyActivityCall = async (
|
||||
endTime: Date,
|
||||
page: number = 1,
|
||||
organizationIds: string[] | null = null,
|
||||
timezoneOffset?: number,
|
||||
) => {
|
||||
return fetchDailyActivity({
|
||||
accessToken,
|
||||
@@ -1850,7 +1840,6 @@ export const organizationDailyActivityCall = async (
|
||||
extraQueryParams: {
|
||||
organization_ids: organizationIds,
|
||||
},
|
||||
timezoneOffset,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1860,7 +1849,6 @@ export const customerDailyActivityCall = async (
|
||||
endTime: Date,
|
||||
page: number = 1,
|
||||
customerIds: string[] | null = null,
|
||||
timezoneOffset?: number,
|
||||
) => {
|
||||
return fetchDailyActivity({
|
||||
accessToken,
|
||||
@@ -1871,7 +1859,6 @@ export const customerDailyActivityCall = async (
|
||||
extraQueryParams: {
|
||||
end_user_ids: customerIds,
|
||||
},
|
||||
timezoneOffset,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1881,7 +1868,6 @@ export const agentDailyActivityCall = async (
|
||||
endTime: Date,
|
||||
page: number = 1,
|
||||
agentIds: string[] | null = null,
|
||||
timezoneOffset?: number,
|
||||
) => {
|
||||
return fetchDailyActivity({
|
||||
accessToken,
|
||||
@@ -1892,7 +1878,6 @@ export const agentDailyActivityCall = async (
|
||||
extraQueryParams: {
|
||||
agent_ids: agentIds,
|
||||
},
|
||||
timezoneOffset,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3196,7 +3181,7 @@ export const keyAliasesCall = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const userDailyActivityAggregatedCall = async (accessToken: string, startTime: Date, endTime: Date, userId: string | null = null, timezoneOffset?: number) => {
|
||||
export const userDailyActivityAggregatedCall = async (accessToken: string, startTime: Date, endTime: Date, userId: string | null = null) => {
|
||||
/**
|
||||
* Get aggregated daily user activity (no pagination)
|
||||
*/
|
||||
@@ -3213,8 +3198,7 @@ export const userDailyActivityAggregatedCall = async (accessToken: string, start
|
||||
queryParams.append("start_date", formatDate(startTime));
|
||||
queryParams.append("end_date", formatDate(endTime));
|
||||
// Send timezone offset so backend can adjust date range for UTC storage
|
||||
const tz = timezoneOffset !== undefined ? timezoneOffset : new Date().getTimezoneOffset();
|
||||
queryParams.append("timezone", tz.toString());
|
||||
queryParams.append("timezone", new Date().getTimezoneOffset().toString());
|
||||
if (userId) {
|
||||
queryParams.append("user_id", userId);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { CalendarOutlined, ClockCircleOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { Select } from "antd";
|
||||
import { CalendarOutlined, ClockCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, DateRangePickerValue, Text } from "@tremor/react";
|
||||
import moment from "moment";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
interface AdvancedDatePickerProps {
|
||||
value: DateRangePickerValue;
|
||||
@@ -10,48 +9,8 @@ interface AdvancedDatePickerProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
showTimeRange?: boolean;
|
||||
timezoneOffset?: number;
|
||||
onTimezoneChange?: (offset: number) => void;
|
||||
}
|
||||
|
||||
const getLocalTimezoneOffset = (): number => new Date().getTimezoneOffset();
|
||||
|
||||
const formatTimezoneLabel = (offsetMinutes: number): string => {
|
||||
if (offsetMinutes === 0) return "UTC";
|
||||
const sign = offsetMinutes > 0 ? "-" : "+";
|
||||
const absMinutes = Math.abs(offsetMinutes);
|
||||
const hours = Math.floor(absMinutes / 60);
|
||||
const minutes = absMinutes % 60;
|
||||
const formatted = minutes > 0 ? `${hours}:${String(minutes).padStart(2, "0")}` : `${hours}`;
|
||||
return `UTC${sign}${formatted}`;
|
||||
};
|
||||
|
||||
// Common timezone options: offset in minutes (JS getTimezoneOffset convention: positive = west of UTC)
|
||||
const TIMEZONE_OPTIONS: { label: string; offset: number }[] = [
|
||||
{ label: "UTC-10 (Hawaii)", offset: 600 },
|
||||
{ label: "UTC-9 (Alaska)", offset: 540 },
|
||||
{ label: "UTC-8 (Pacific)", offset: 480 },
|
||||
{ label: "UTC-7 (Mountain)", offset: 420 },
|
||||
{ label: "UTC-6 (Central)", offset: 360 },
|
||||
{ label: "UTC-5 (Eastern)", offset: 300 },
|
||||
{ label: "UTC-4 (Atlantic)", offset: 240 },
|
||||
{ label: "UTC-3 (Buenos Aires)", offset: 180 },
|
||||
{ label: "UTC (London/UTC)", offset: 0 },
|
||||
{ label: "UTC+1 (Central Europe)", offset: -60 },
|
||||
{ label: "UTC+2 (Eastern Europe)", offset: -120 },
|
||||
{ label: "UTC+3 (Moscow)", offset: -180 },
|
||||
{ label: "UTC+4 (Dubai)", offset: -240 },
|
||||
{ label: "UTC+5 (Pakistan)", offset: -300 },
|
||||
{ label: "UTC+5:30 (India)", offset: -330 },
|
||||
{ label: "UTC+5:45 (Nepal)", offset: -345 },
|
||||
{ label: "UTC+6 (Bangladesh)", offset: -360 },
|
||||
{ label: "UTC+7 (Bangkok)", offset: -420 },
|
||||
{ label: "UTC+8 (Singapore)", offset: -480 },
|
||||
{ label: "UTC+9 (Japan/Korea)", offset: -540 },
|
||||
{ label: "UTC+10 (Sydney)", offset: -600 },
|
||||
{ label: "UTC+12 (Auckland)", offset: -720 },
|
||||
];
|
||||
|
||||
interface RelativeTimeOption {
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
@@ -109,22 +68,7 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
||||
onValueChange,
|
||||
label = "Select Time Range",
|
||||
showTimeRange = true,
|
||||
timezoneOffset,
|
||||
onTimezoneChange,
|
||||
}) => {
|
||||
const timezoneOptions = useMemo(() => {
|
||||
const localOffset = getLocalTimezoneOffset();
|
||||
const hasLocalInList = TIMEZONE_OPTIONS.some((tz) => tz.offset === localOffset);
|
||||
const opts = TIMEZONE_OPTIONS.map((tz) => ({
|
||||
value: tz.offset,
|
||||
label: tz.label + (tz.offset === localOffset ? " (Local)" : ""),
|
||||
}));
|
||||
if (!hasLocalInList) {
|
||||
opts.unshift({ value: localOffset, label: `Local (${formatTimezoneLabel(localOffset)})` });
|
||||
}
|
||||
return opts;
|
||||
}, []);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [tempValue, setTempValue] = useState<DateRangePickerValue>(value);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
@@ -134,7 +78,6 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
||||
const [endDate, setEndDate] = useState("");
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const settingsPanelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Function to check if current value matches a relative time option
|
||||
const getMatchingOption = useCallback((currentValue: DateRangePickerValue): string | null => {
|
||||
@@ -400,29 +343,6 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6 pb-20">
|
||||
{/* Timezone selector */}
|
||||
{onTimezoneChange && (
|
||||
<div ref={settingsPanelRef}>
|
||||
<label className="text-sm text-gray-700 mb-1 block">Timezone</label>
|
||||
<Select
|
||||
showSearch
|
||||
value={timezoneOffset !== undefined ? timezoneOffset : getLocalTimezoneOffset()}
|
||||
onChange={(val) => onTimezoneChange(val)}
|
||||
options={timezoneOptions}
|
||||
popupMatchSelectWidth={false}
|
||||
listHeight={200}
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
getPopupContainer={() => settingsPanelRef.current!}
|
||||
suffixIcon={null}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
className="w-full"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Start date */}
|
||||
<div>
|
||||
<label className="text-sm text-gray-700 mb-1 block">Start date</label>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user