[QA] Viewing Agent Activity Headers on UI Usage Page (#13212)

* qa - agents

* refactored WAU, MAU and DAU endpoints

* fixes for dau, wau, mau

* use stack=true

* fixes for DAU calc

* fixes for rendering WAU, MAU

* use 1 section for topline

* Fixes for endpoint

* remove filter

* fix spacing

* fix activity

* working UI rendering

* fixes for chart data

* allow selecting specific tags

* add DistinctTagResponse endpoints

* use wide selector

* add types

* fixes for UI rendering

* get_per_user_analytics
This commit is contained in:
Ishaan Jaff 2025-08-01 15:29:13 -07:00 committed by GitHub
parent 3d02d3602e
commit 4a85a91f20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1152 additions and 690 deletions

View file

@ -6498,18 +6498,239 @@ export const userAgentAnalyticsCall = async (
}
};
// New endpoint functions for DAU, WAU, MAU
export const tagDauCall = async (
accessToken: string,
endDate: Date,
tagFilter?: string,
tagFilters?: string[]
) => {
/**
* Get Daily Active Users (DAU) for last 7 days ending on endDate
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/tag/dau`
: `/tag/dau`;
const queryParams = new URLSearchParams();
// Format date 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("end_date", formatDate(endDate));
// Handle multiple tag filters (takes precedence over single tag filter)
if (tagFilters && tagFilters.length > 0) {
tagFilters.forEach(tag => {
queryParams.append("tag_filters", tag);
});
} else if (tagFilter) {
queryParams.append("tag_filter", tagFilter);
}
const queryString = queryParams.toString();
if (queryString) {
url += `?${queryString}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch DAU:", error);
throw error;
}
};
export const tagWauCall = async (
accessToken: string,
endDate: Date,
tagFilter?: string,
tagFilters?: string[]
) => {
/**
* Get Weekly Active Users (WAU) for last 7 weeks ending on endDate
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/tag/wau`
: `/tag/wau`;
const queryParams = new URLSearchParams();
// Format date 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("end_date", formatDate(endDate));
// Handle multiple tag filters (takes precedence over single tag filter)
if (tagFilters && tagFilters.length > 0) {
tagFilters.forEach(tag => {
queryParams.append("tag_filters", tag);
});
} else if (tagFilter) {
queryParams.append("tag_filter", tagFilter);
}
const queryString = queryParams.toString();
if (queryString) {
url += `?${queryString}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch WAU:", error);
throw error;
}
};
export const tagMauCall = async (
accessToken: string,
endDate: Date,
tagFilter?: string,
tagFilters?: string[]
) => {
/**
* Get Monthly Active Users (MAU) for last 7 months ending on endDate
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/tag/mau`
: `/tag/mau`;
const queryParams = new URLSearchParams();
// Format date 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("end_date", formatDate(endDate));
// Handle multiple tag filters (takes precedence over single tag filter)
if (tagFilters && tagFilters.length > 0) {
tagFilters.forEach(tag => {
queryParams.append("tag_filters", tag);
});
} else if (tagFilter) {
queryParams.append("tag_filter", tagFilter);
}
const queryString = queryParams.toString();
if (queryString) {
url += `?${queryString}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch MAU:", error);
throw error;
}
};
export const tagDistinctCall = async (
accessToken: string
) => {
/**
* Get all distinct user agent tags (up to 250)
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/tag/distinct`
: `/tag/distinct`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch distinct tags:", error);
throw error;
}
};
export const userAgentSummaryCall = async (
accessToken: string,
startTime: Date,
endTime: Date
endTime: Date,
tagFilters?: string[]
) => {
/**
* Get user agent summary statistics
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/tag/user-agent/summary`
: `/tag/user-agent/summary`;
? `${proxyBaseUrl}/tag/summary`
: `/tag/summary`;
const queryParams = new URLSearchParams();
@ -6524,6 +6745,13 @@ export const userAgentSummaryCall = async (
queryParams.append("start_date", formatDate(startTime));
queryParams.append("end_date", formatDate(endTime));
// Handle multiple tag filters
if (tagFilters && tagFilters.length > 0) {
tagFilters.forEach(tag => {
queryParams.append("tag_filters", tag);
});
}
const queryString = queryParams.toString();
if (queryString) {
url += `?${queryString}`;
@ -6553,13 +6781,12 @@ export const userAgentSummaryCall = async (
export const perUserAnalyticsCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
pageSize: number = 50
pageSize: number = 50,
tagFilters?: string[]
) => {
/**
* Get per-user analytics data
* Get per-user analytics data for the last 30 days
*/
try {
let url = proxyBaseUrl
@ -6568,19 +6795,16 @@ export const perUserAnalyticsCall = async (
const queryParams = new URLSearchParams();
// 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", page.toString());
queryParams.append("page_size", pageSize.toString());
// Handle multiple tag filters
if (tagFilters && tagFilters.length > 0) {
tagFilters.forEach(tag => {
queryParams.append("tag_filters", tag);
});
}
const queryString = queryParams.toString();
if (queryString) {
url += `?${queryString}`;

View file

@ -42,13 +42,13 @@ interface PerUserAnalyticsResponse {
interface PerUserUsageProps {
accessToken: string | null;
dateValue: DateRangePickerValue;
selectedTags: string[];
formatAbbreviatedNumber: (value: number, decimalPlaces?: number) => string;
}
const PerUserUsage: React.FC<PerUserUsageProps> = ({
accessToken,
dateValue,
selectedTags,
formatAbbreviatedNumber,
}) => {
const [perUserData, setPerUserData] = useState<PerUserAnalyticsResponse>({
@ -63,16 +63,15 @@ const PerUserUsage: React.FC<PerUserUsageProps> = ({
const [currentPage, setCurrentPage] = useState(1);
const fetchPerUserData = async () => {
if (!accessToken || !dateValue.from || !dateValue.to) return;
if (!accessToken) return;
setLoading(true);
try {
const response = await perUserAnalyticsCall(
accessToken,
dateValue.from,
dateValue.to,
currentPage,
50
50,
selectedTags.length > 0 ? selectedTags : undefined
);
setPerUserData(response);
} catch (error) {
@ -84,7 +83,7 @@ const PerUserUsage: React.FC<PerUserUsageProps> = ({
useEffect(() => {
fetchPerUserData();
}, [accessToken, dateValue, currentPage]);
}, [accessToken, selectedTags, currentPage]);
const handleNextPage = () => {
if (currentPage < perUserData.total_pages) {

View file

@ -15,8 +15,6 @@ import {
DonutChart,
Metric,
Subtitle,
Select,
SelectItem,
Button,
Tab,
TabGroup,
@ -24,55 +22,47 @@ import {
TabPanel,
TabPanels,
} from "@tremor/react";
import { Select } from "antd";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { userAgentAnalyticsCall, userAgentSummaryCall } from "./networking";
import { userAgentSummaryCall, tagDauCall, tagWauCall, tagMauCall, tagDistinctCall } from "./networking";
import AdvancedDatePicker from "./shared/advanced_date_picker";
import PerUserUsage from "./per_user_usage";
import { DateRangePickerValue } from "@tremor/react";
import { ChartLoader } from "./shared/chart_loader";
interface UserAgentMetrics {
dau: number;
wau: number;
mau: number;
// New interfaces for the updated API response
interface TagActiveUsersResponse {
tag: string;
active_users: number;
date: string;
period_start?: string;
period_end?: string;
}
interface ActiveUsersAnalyticsResponse {
results: TagActiveUsersResponse[];
}
interface TagSummaryMetrics {
tag: string;
unique_users: number;
total_requests: number;
successful_requests: number;
failed_requests: number;
total_requests: number;
completed_tokens: number;
total_tokens: number;
spend: number;
}
interface UserAgentActivityData {
date: string;
tag: string;
user_agent: string;
metrics: UserAgentMetrics;
}
interface UserAgentAnalyticsResponse {
results: UserAgentActivityData[];
total_count: number;
page: number;
page_size: number;
total_pages: number;
}
interface UserAgentSummaryData {
total_tags: number;
total_requests: number;
total_successful_requests: number;
total_failed_requests: number;
total_tokens: number;
total_spend: number;
top_tags: Array<{
tag: string;
requests: number;
successful_requests: number;
failed_requests: number;
tokens: number;
spend: number;
}>;
}
interface TagSummaryResponse {
results: TagSummaryMetrics[];
}
interface DistinctTagResponse {
tag: string;
}
interface DistinctTagsResponse {
results: DistinctTagResponse[];
}
interface UserAgentActivityProps {
@ -84,23 +74,11 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
accessToken,
userRole,
}) => {
const [analyticsData, setAnalyticsData] = useState<UserAgentAnalyticsResponse>({
results: [],
total_count: 0,
page: 1,
page_size: 50,
total_pages: 0,
});
const [summaryData, setSummaryData] = useState<UserAgentSummaryData>({
total_tags: 0,
total_requests: 0,
total_successful_requests: 0,
total_failed_requests: 0,
total_tokens: 0,
total_spend: 0,
top_tags: [],
});
// Separate state for each endpoint
const [dauData, setDauData] = useState<ActiveUsersAnalyticsResponse>({ results: [] });
const [wauData, setWauData] = useState<ActiveUsersAnalyticsResponse>({ results: [] });
const [mauData, setMauData] = useState<ActiveUsersAnalyticsResponse>({ results: [] });
const [summaryData, setSummaryData] = useState<TagSummaryResponse>({ results: [] });
const [dateValue, setDateValue] = useState<DateRangePickerValue>({
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
@ -108,31 +86,91 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
});
const [userAgentFilter, setUserAgentFilter] = useState<string>("");
const [analyticsLoading, setAnalyticsLoading] = useState(false);
// Tag filtering state
const [availableTags, setAvailableTags] = useState<string[]>([]);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [tagsLoading, setTagsLoading] = useState(false);
// Separate loading states for each endpoint
const [dauLoading, setDauLoading] = useState(false);
const [wauLoading, setWauLoading] = useState(false);
const [mauLoading, setMauLoading] = useState(false);
const [summaryLoading, setSummaryLoading] = useState(false);
const [isDateChanging, setIsDateChanging] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const fetchAnalyticsData = async () => {
if (!accessToken || !dateValue.from || !dateValue.to) return;
// Use today's date as the end date for all API calls
const today = new Date();
setAnalyticsLoading(true);
const fetchAvailableTags = async () => {
if (!accessToken) return;
setTagsLoading(true);
try {
const analytics = await userAgentAnalyticsCall(
accessToken,
dateValue.from,
dateValue.to,
currentPage,
50,
userAgentFilter || undefined
);
setAnalyticsData(analytics);
const data = await tagDistinctCall(accessToken);
setAvailableTags(data.results.map((item: DistinctTagResponse) => item.tag));
} catch (error) {
console.error("Failed to fetch user agent analytics data:", error);
console.error("Failed to fetch available tags:", error);
} finally {
setAnalyticsLoading(false);
setIsDateChanging(false);
setTagsLoading(false);
}
};
const fetchDauData = async () => {
if (!accessToken) return;
setDauLoading(true);
try {
const data = await tagDauCall(
accessToken,
today,
userAgentFilter || undefined,
selectedTags.length > 0 ? selectedTags : undefined
);
setDauData(data);
} catch (error) {
console.error("Failed to fetch DAU data:", error);
} finally {
setDauLoading(false);
}
};
const fetchWauData = async () => {
if (!accessToken) return;
setWauLoading(true);
try {
const data = await tagWauCall(
accessToken,
today,
userAgentFilter || undefined,
selectedTags.length > 0 ? selectedTags : undefined
);
setWauData(data);
} catch (error) {
console.error("Failed to fetch WAU data:", error);
} finally {
setWauLoading(false);
}
};
const fetchMauData = async () => {
if (!accessToken) return;
setMauLoading(true);
try {
const data = await tagMauCall(
accessToken,
today,
userAgentFilter || undefined,
selectedTags.length > 0 ? selectedTags : undefined
);
setMauData(data);
} catch (error) {
console.error("Failed to fetch MAU data:", error);
} finally {
setMauLoading(false);
}
};
@ -141,7 +179,12 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
setSummaryLoading(true);
try {
const summary = await userAgentSummaryCall(accessToken, dateValue.from, dateValue.to);
const summary = await userAgentSummaryCall(
accessToken,
dateValue.from,
dateValue.to,
selectedTags.length > 0 ? selectedTags : undefined
);
setSummaryData(summary);
} catch (error) {
console.error("Failed to fetch user agent summary data:", error);
@ -155,69 +198,40 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
const handleDateChange = (newValue: DateRangePickerValue) => {
// Instant visual feedback
setIsDateChanging(true);
setAnalyticsLoading(true);
setSummaryLoading(true);
// Update date immediately for UI responsiveness
setDateValue(newValue);
setCurrentPage(1); // Reset to first page when date changes
};
// Debounced effect for data fetching
// Effect to fetch available tags on mount
useEffect(() => {
if (!dateValue.from || !dateValue.to) return;
fetchAvailableTags();
}, [accessToken]);
// Effect for DAU/WAU/MAU data (independent of date picker)
useEffect(() => {
if (!accessToken) return;
const timeoutId = setTimeout(() => {
// Call both fetch functions independently
fetchAnalyticsData();
fetchSummaryData();
}, 50); // Very short debounce
return () => clearTimeout(timeoutId);
}, [accessToken, dateValue, userAgentFilter]);
// Separate effect for pagination that only affects analytics
useEffect(() => {
if (!dateValue.from || !dateValue.to) return;
const timeoutId = setTimeout(() => {
fetchAnalyticsData();
fetchDauData();
fetchWauData();
fetchMauData();
}, 50);
return () => clearTimeout(timeoutId);
}, [currentPage]);
}, [accessToken, userAgentFilter, selectedTags]);
// Aggregate data by user agent for charts
const aggregatedByUserAgent = analyticsData.results.reduce((acc, item) => {
const ua = item.user_agent || "Unknown";
if (!acc[ua]) {
acc[ua] = {
user_agent: ua,
total_requests: 0,
successful_requests: 0,
failed_requests: 0,
total_tokens: 0,
spend: 0,
dau: 0,
wau: 0,
mau: 0,
};
}
acc[ua].total_requests += item.metrics.total_requests;
acc[ua].successful_requests += item.metrics.successful_requests;
acc[ua].failed_requests += item.metrics.failed_requests;
acc[ua].total_tokens += item.metrics.total_tokens;
acc[ua].spend += item.metrics.spend;
// For user counts, take the maximum to avoid double counting
acc[ua].dau = Math.max(acc[ua].dau, item.metrics.dau);
acc[ua].wau = Math.max(acc[ua].wau, item.metrics.wau);
acc[ua].mau = Math.max(acc[ua].mau, item.metrics.mau);
return acc;
}, {} as Record<string, any>);
// Effect for summary data (depends on date picker)
useEffect(() => {
if (!dateValue.from || !dateValue.to) return;
const chartData = Object.values(aggregatedByUserAgent).sort(
(a: any, b: any) => b.total_requests - a.total_requests
);
const timeoutId = setTimeout(() => {
fetchSummaryData();
}, 50);
return () => clearTimeout(timeoutId);
}, [accessToken, dateValue, selectedTags]);
// Helper function to extract user agent from tag
const extractUserAgent = (tag: string): string => {
@ -235,62 +249,133 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
return userAgent;
};
const successRateData = (summaryData.top_tags || []).map((tag) => ({
user_agent: extractUserAgent(tag.tag),
success_rate: tag.successful_requests / (tag.requests || 1) * 100,
total_requests: tag.requests,
}));
// Get unique user agents for chart
const uniqueUserAgents = Array.from(
new Set(analyticsData.results.map(item => item.user_agent || "Unknown"))
).slice(0, 3); // Top 3 user agents
// Prepare daily chart data (DAU)
const dailyChartData = analyticsData.results.reduce((acc, item) => {
const existingDate = acc.find(d => d.date === item.date);
if (existingDate) {
existingDate[item.user_agent || "Unknown"] = item.metrics.dau;
} else {
const newDateEntry: any = {
date: item.date,
[item.user_agent || "Unknown"]: item.metrics.dau
};
acc.push(newDateEntry);
// Get all user agents for each chart type based on their specific data
const getAllTagsForData = (data: TagActiveUsersResponse[]) => {
// Aggregate total active users per tag
const tagTotals = data.reduce((acc, item) => {
acc[item.tag] = (acc[item.tag] || 0) + item.active_users;
return acc;
}, {} as Record<string, number>);
// Sort by total active users and return all tags
return Object.entries(tagTotals)
.sort(([, a], [, b]) => b - a)
.map(([tag]) => tag);
};
const allDauTags = getAllTagsForData(dauData.results);
const allWauTags = getAllTagsForData(wauData.results);
const allMauTags = getAllTagsForData(mauData.results);
// Prepare daily chart data (DAU) - always show last 7 days
const generateDailyChartData = () => {
const chartData: any[] = [];
const endDate = new Date();
// Generate all 7 days
for (let i = 6; i >= 0; i--) {
const date = new Date(endDate);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD format
const dayEntry: any = { date: dateStr };
// Initialize all user agents to 0
allDauTags.forEach(tag => {
const userAgent = extractUserAgent(tag);
dayEntry[userAgent] = 0;
});
chartData.push(dayEntry);
}
return acc;
}, [] as any[]);
// Fill in actual data
dauData.results.forEach(item => {
const userAgent = extractUserAgent(item.tag);
const dayEntry = chartData.find(d => d.date === item.date);
if (dayEntry) {
dayEntry[userAgent] = item.active_users;
}
});
return chartData;
};
const dailyChartData = generateDailyChartData();
// Prepare weekly chart data (WAU)
const weeklyChartData = analyticsData.results.reduce((acc, item) => {
const existingDate = acc.find(d => d.week === item.date);
if (existingDate) {
existingDate[item.user_agent || "Unknown"] = item.metrics.wau;
} else {
const newDateEntry: any = {
week: `Week ${acc.length + 1}`,
[item.user_agent || "Unknown"]: item.metrics.wau
};
acc.push(newDateEntry);
// Prepare weekly chart data (WAU) - always show all 7 weeks
const generateWeeklyChartData = () => {
const chartData: any[] = [];
// Generate all 7 weeks (Week 1 through Week 7)
for (let weekNum = 1; weekNum <= 7; weekNum++) {
const weekEntry: any = { week: `Week ${weekNum}` };
// Initialize all user agents to 0
allWauTags.forEach(tag => {
const userAgent = extractUserAgent(tag);
weekEntry[userAgent] = 0;
});
chartData.push(weekEntry);
}
return acc;
}, [] as any[]);
// Fill in actual data
wauData.results.forEach(item => {
const userAgent = extractUserAgent(item.tag);
// Extract week number from the date field (e.g., "Week 1 (Jul 27)" -> "Week 1")
const weekMatch = item.date.match(/Week (\d+)/);
if (weekMatch) {
const weekLabel = `Week ${weekMatch[1]}`;
const weekEntry = chartData.find(d => d.week === weekLabel);
if (weekEntry) {
weekEntry[userAgent] = item.active_users;
}
}
});
return chartData;
};
const weeklyChartData = generateWeeklyChartData();
// Prepare monthly chart data (MAU)
const monthlyChartData = analyticsData.results.reduce((acc, item) => {
const existingDate = acc.find(d => d.month === item.date);
if (existingDate) {
existingDate[item.user_agent || "Unknown"] = item.metrics.mau;
} else {
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"];
const newDateEntry: any = {
month: monthNames[acc.length % 7] || `Month ${acc.length + 1}`,
[item.user_agent || "Unknown"]: item.metrics.mau
};
acc.push(newDateEntry);
// Prepare monthly chart data (MAU) - always show all 7 months
const generateMonthlyChartData = () => {
const chartData: any[] = [];
// Generate all 7 months (Month 1 through Month 7)
for (let monthNum = 1; monthNum <= 7; monthNum++) {
const monthEntry: any = { month: `Month ${monthNum}` };
// Initialize all user agents to 0
allMauTags.forEach(tag => {
const userAgent = extractUserAgent(tag);
monthEntry[userAgent] = 0;
});
chartData.push(monthEntry);
}
return acc;
}, [] as any[]);
// Fill in actual data
mauData.results.forEach(item => {
const userAgent = extractUserAgent(item.tag);
// Extract month number from the date field (e.g., "Month 1 (Jul)" -> "Month 1")
const monthMatch = item.date.match(/Month (\d+)/);
if (monthMatch) {
const monthLabel = `Month ${monthMatch[1]}`;
const monthEntry = chartData.find(d => d.month === monthLabel);
if (monthEntry) {
monthEntry[userAgent] = item.active_users;
}
}
});
return chartData;
};
const monthlyChartData = generateMonthlyChartData();
// Format numbers with K, M abbreviations
const formatAbbreviatedNumber = (value: number, decimalPlaces: number = 0): string => {
@ -310,93 +395,112 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
};
return (
<div className="space-y-6">
{/* Date Range Picker */}
<Grid numItems={2} className="gap-2 w-full">
<Col>
<div className="space-y-6 mt-6">
{/* Summary Section Card */}
<Card>
<div className="space-y-6">
<div className="flex justify-between items-start">
<div>
<Title>Summary by User Agent</Title>
<Subtitle>Performance metrics for different user agents</Subtitle>
</div>
{/* User Agent Filter */}
<div className="w-96">
<Text className="text-sm font-medium block mb-2">Filter by User Agents</Text>
<Select
mode="multiple"
placeholder="All User Agents"
value={selectedTags}
onChange={setSelectedTags}
style={{ width: "100%" }}
showSearch={true}
allowClear={true}
loading={tagsLoading}
optionFilterProp="label"
className="rounded-md"
maxTagCount="responsive"
>
{availableTags.map((tag) => {
const userAgent = extractUserAgent(tag);
const displayName = userAgent.length > 50 ? `${userAgent.substring(0, 50)}...` : userAgent;
return (
<Select.Option key={tag} value={tag} label={displayName} title={userAgent}>
{displayName}
</Select.Option>
);
})}
</Select>
</div>
</div>
{/* Date Range Picker within Summary */}
<AdvancedDatePicker
value={dateValue}
onValueChange={handleDateChange}
/>
</Col>
<Col>
<Select
value={userAgentFilter}
onValueChange={setUserAgentFilter}
placeholder="Filter by User Agent"
>
<SelectItem value="">All User Agents</SelectItem>
<SelectItem value="curl">curl</SelectItem>
<SelectItem value="litellm">litellm</SelectItem>
<SelectItem value="python">python</SelectItem>
<SelectItem value="postman">postman</SelectItem>
<SelectItem value="axios">axios</SelectItem>
<SelectItem value="fetch">fetch</SelectItem>
</Select>
</Col>
</Grid>
{/* Top 4 User Agents Cards */}
{summaryLoading ? (
<Card>
<ChartLoader isDateChanging={isDateChanging} />
</Card>
) : (
<Grid numItems={4} className="gap-4">
{(summaryData.top_tags || []).slice(0, 4).map((tag, index) => {
const userAgent = extractUserAgent(tag.tag);
const displayName = truncateUserAgent(userAgent);
return (
<Card key={index}>
<Title className="truncate" title={userAgent}>
{displayName}
</Title>
<div className="mt-4 space-y-3">
<div>
<Text className="text-sm text-gray-600">Success Requests</Text>
<Metric className="text-lg">{formatAbbreviatedNumber(tag.successful_requests)}</Metric>
{/* Top 4 User Agents Cards */}
{summaryLoading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<Grid numItems={4} className="gap-4">
{(summaryData.results || []).slice(0, 4).map((tag, index) => {
const userAgent = extractUserAgent(tag.tag);
const displayName = truncateUserAgent(userAgent);
return (
<Card key={index}>
<Title className="truncate" title={userAgent}>
{displayName}
</Title>
<div className="mt-4 space-y-3">
<div>
<Text className="text-sm text-gray-600">Success Requests</Text>
<Metric className="text-lg">{formatAbbreviatedNumber(tag.successful_requests)}</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Tokens</Text>
<Metric className="text-lg">{formatAbbreviatedNumber(tag.total_tokens)}</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Cost</Text>
<Metric className="text-lg">${formatAbbreviatedNumber(tag.total_spend, 4)}</Metric>
</div>
</div>
</Card>
);
})}
{/* Fill remaining slots if less than 4 agents */}
{Array.from({ length: Math.max(0, 4 - (summaryData.results || []).length) }).map((_, index) => (
<Card key={`empty-${index}`}>
<Title>No Data</Title>
<div className="mt-4 space-y-3">
<div>
<Text className="text-sm text-gray-600">Success Requests</Text>
<Metric className="text-lg">-</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Tokens</Text>
<Metric className="text-lg">-</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Cost</Text>
<Metric className="text-lg">-</Metric>
</div>
</div>
<div>
<Text className="text-sm text-gray-600">Total Tokens</Text>
<Metric className="text-lg">{formatAbbreviatedNumber(tag.tokens)}</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Cost</Text>
<Metric className="text-lg">${formatAbbreviatedNumber(tag.spend, 4)}</Metric>
</div>
</div>
</Card>
);
})}
{/* Fill remaining slots if less than 4 agents */}
{Array.from({ length: Math.max(0, 4 - (summaryData.top_tags || []).length) }).map((_, index) => (
<Card key={`empty-${index}`}>
<Title>No Data</Title>
<div className="mt-4 space-y-3">
<div>
<Text className="text-sm text-gray-600">Success Requests</Text>
<Metric className="text-lg">-</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Tokens</Text>
<Metric className="text-lg">-</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Cost</Text>
<Metric className="text-lg">-</Metric>
</div>
</div>
</Card>
))}
</Grid>
)}
</Card>
))}
</Grid>
)}
</div>
</Card>
{/* Main TabGroup for DAU/WAU/MAU vs Per User Usage */}
<Card>
<TabGroup>
<TabList className="mb-6">
<Tab>DAU/WAU/MAU</Tab>
<Tab>Per User Usage</Tab>
<Tab>Per User Usage (Last 30 Days)</Tab>
</TabList>
<TabPanels>
@ -419,36 +523,36 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
<div className="mb-4">
<Title className="text-lg">Daily Active Users - Last 7 Days</Title>
</div>
{analyticsLoading ? (
<ChartLoader isDateChanging={isDateChanging} />
{dauLoading ? (
<ChartLoader isDateChanging={false} />
) : (
<BarChart
data={dailyChartData}
index="date"
categories={uniqueUserAgents.slice(0, 3)}
colors={["blue", "green", "orange"]}
categories={allDauTags.map(extractUserAgent)}
valueFormatter={(value: number) => formatAbbreviatedNumber(value)}
yAxisWidth={60}
showLegend={true}
stack={true}
/>
)}
</TabPanel>
<TabPanel>
<div className="mb-4">
<Title className="text-lg">Weekly Active Users - Last 4 Weeks</Title>
<Title className="text-lg">Weekly Active Users - Last 7 Weeks</Title>
</div>
{analyticsLoading ? (
<ChartLoader isDateChanging={isDateChanging} />
{wauLoading ? (
<ChartLoader isDateChanging={false} />
) : (
<BarChart
data={weeklyChartData}
index="week"
categories={uniqueUserAgents.slice(0, 3)}
colors={["blue", "green", "orange"]}
categories={allWauTags.map(extractUserAgent)}
valueFormatter={(value: number) => formatAbbreviatedNumber(value)}
yAxisWidth={60}
showLegend={true}
stack={true}
/>
)}
</TabPanel>
@ -457,17 +561,17 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
<div className="mb-4">
<Title className="text-lg">Monthly Active Users - Last 7 Months</Title>
</div>
{analyticsLoading ? (
<ChartLoader isDateChanging={isDateChanging} />
{mauLoading ? (
<ChartLoader isDateChanging={false} />
) : (
<BarChart
data={monthlyChartData}
index="month"
categories={uniqueUserAgents.slice(0, 3)}
colors={["blue", "green", "orange"]}
categories={allMauTags.map(extractUserAgent)}
valueFormatter={(value: number) => formatAbbreviatedNumber(value)}
yAxisWidth={60}
showLegend={true}
stack={true}
/>
)}
</TabPanel>
@ -479,7 +583,7 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({
<TabPanel>
<PerUserUsage
accessToken={accessToken}
dateValue={dateValue}
selectedTags={selectedTags}
formatAbbreviatedNumber={formatAbbreviatedNumber}
/>
</TabPanel>