mirror of
https://github.com/onyx-dot-app/litellm.git
synced 2026-07-01 20:44:04 -04:00
Fixed Log Tab Key Alias filtering inaccurately for failed logs
This commit is contained in:
@@ -1659,6 +1659,9 @@ async def ui_view_spend_logs( # noqa: PLR0915
|
||||
model: Optional[str] = fastapi.Query(
|
||||
default=None, description="Filter logs by model"
|
||||
),
|
||||
key_alias: Optional[str] = fastapi.Query(
|
||||
default=None, description="Filter logs by key alias"
|
||||
),
|
||||
):
|
||||
"""
|
||||
View spend logs for UI with pagination support
|
||||
@@ -1726,6 +1729,12 @@ async def ui_view_spend_logs( # noqa: PLR0915
|
||||
|
||||
if model is not None:
|
||||
where_conditions["model"] = model
|
||||
|
||||
if key_alias is not None:
|
||||
where_conditions["metadata"] = {
|
||||
"path": ["user_api_key_alias"],
|
||||
"string_contains": key_alias
|
||||
}
|
||||
|
||||
if min_spend is not None or max_spend is not None:
|
||||
where_conditions["spend"] = {}
|
||||
|
||||
Generated
+7975
-4
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -5,6 +5,9 @@
|
||||
"react-copy-to-clipboard": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-copy-to-clipboard": "^5.0.7"
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^14.3.1",
|
||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||
"jest": "^29.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { uiSpendLogsCall } from '../../../ui/litellm-dashboard/src/components/networking';
|
||||
|
||||
// Mock the networking module
|
||||
jest.mock('../../../ui/litellm-dashboard/src/components/networking', () => ({
|
||||
uiSpendLogsCall: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUiSpendLogsCall = uiSpendLogsCall as jest.MockedFunction<typeof uiSpendLogsCall>;
|
||||
|
||||
describe('Key Alias Filtering Integration Test', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call API with correct key_alias parameter', async () => {
|
||||
// Mock API response with both success and failure logs
|
||||
const mockResponse = {
|
||||
data: [
|
||||
{ request_id: 'req-1', status: 'success', metadata: { user_api_key_alias: 'test-key' } },
|
||||
{ request_id: 'req-2', status: 'failure', metadata: { user_api_key_alias: 'test-key' } }
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
total_pages: 1
|
||||
};
|
||||
|
||||
mockUiSpendLogsCall.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
// Simulate the API call that would happen when filtering by key alias
|
||||
const result = await uiSpendLogsCall(
|
||||
'test-token',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'2024-01-15 09:00:00',
|
||||
'2024-01-15 11:00:00',
|
||||
1,
|
||||
50,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'test-key-alias' // key_alias - this is the fix
|
||||
);
|
||||
|
||||
// Verify the API was called correctly
|
||||
expect(mockUiSpendLogsCall).toHaveBeenCalledWith(
|
||||
'test-token',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'2024-01-15 09:00:00',
|
||||
'2024-01-15 11:00:00',
|
||||
1,
|
||||
50,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'test-key-alias' // The key assertion - this parameter should be passed through
|
||||
);
|
||||
|
||||
// Verify response contains both success and failure logs
|
||||
expect(result.data).toHaveLength(2);
|
||||
expect(result.data[0].status).toBe('success');
|
||||
expect(result.data[1].status).toBe('failure');
|
||||
});
|
||||
|
||||
it('should pass undefined for empty key alias', async () => {
|
||||
mockUiSpendLogsCall.mockResolvedValueOnce({ data: [], total: 0, page: 1, page_size: 50, total_pages: 0 });
|
||||
|
||||
await uiSpendLogsCall(
|
||||
'test-token', undefined, undefined, undefined,
|
||||
'2024-01-15 09:00:00', '2024-01-15 11:00:00',
|
||||
1, 50, undefined, undefined, undefined, undefined,
|
||||
undefined // Empty string should become undefined
|
||||
);
|
||||
|
||||
expect(mockUiSpendLogsCall).toHaveBeenCalledWith(
|
||||
'test-token', undefined, undefined, undefined,
|
||||
'2024-01-15 09:00:00', '2024-01-15 11:00:00',
|
||||
1, 50, undefined, undefined, undefined, undefined,
|
||||
undefined // Should be undefined for empty key alias
|
||||
);
|
||||
});
|
||||
});
|
||||
+91
-12
@@ -8,13 +8,14 @@
|
||||
"name": "ui-unit-tests",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"antd": "^5.0.0",
|
||||
"@ant-design/icons": "^5.0.0",
|
||||
"antd": "^5.12.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/antd": "^1.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
@@ -25,6 +26,13 @@
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
|
||||
"integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
@@ -1167,6 +1175,33 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz",
|
||||
"integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.4.0",
|
||||
"aria-query": "^5.0.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"dom-accessibility-api": "^0.6.3",
|
||||
"picocolors": "^1.1.1",
|
||||
"redent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
||||
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/react": {
|
||||
"version": "14.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz",
|
||||
@@ -1194,16 +1229,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/antd": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/antd/-/antd-1.0.4.tgz",
|
||||
"integrity": "sha512-gp4PGQckP1kNjj2H6juhjKIVwkpXwCIyIvOlwp2DC6geuhVpDHEEB5gwH4hJabVgBAFtrjBPJ58VIRV9VV9W2g==",
|
||||
"deprecated": "This is a stub types definition. antd provides its own type definitions, so you do not need this installed.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"antd": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
@@ -2081,6 +2106,13 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css.escape": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cssom": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
||||
@@ -2974,6 +3006,16 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/indent-string": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -4559,6 +4601,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -5552,6 +5604,20 @@
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/redent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"indent-string": "^4.0.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
@@ -5965,6 +6031,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"min-indent": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
|
||||
@@ -2633,7 +2633,8 @@ export const uiSpendLogsCall = async (
|
||||
user_id?: string,
|
||||
end_user?: string,
|
||||
status_filter?: string,
|
||||
model?: string
|
||||
model?: string,
|
||||
keyAlias?: string
|
||||
) => {
|
||||
try {
|
||||
// Construct base URL
|
||||
@@ -2652,6 +2653,7 @@ export const uiSpendLogsCall = async (
|
||||
if (end_user) queryParams.append("end_user", end_user);
|
||||
if (status_filter) queryParams.append("status_filter", status_filter);
|
||||
if (model) queryParams.append("model", model);
|
||||
if (keyAlias) queryParams.append("key_alias", keyAlias);
|
||||
// Append query parameters to URL if any exist
|
||||
const queryString = queryParams.toString();
|
||||
if (queryString) {
|
||||
|
||||
@@ -60,6 +60,7 @@ export function useLogFilterLogic({
|
||||
const performSearch = useCallback(async (filters: LogFilterState, page = 1) => {
|
||||
if (!accessToken) return;
|
||||
|
||||
console.log("Filters being sent to API:", filters);
|
||||
const currentTimestamp = Date.now();
|
||||
lastSearchTimestamp.current = currentTimestamp;
|
||||
|
||||
@@ -81,7 +82,8 @@ export function useLogFilterLogic({
|
||||
filters[FILTER_KEYS.USER_ID] || undefined,
|
||||
filters[FILTER_KEYS.END_USER] || undefined,
|
||||
filters[FILTER_KEYS.STATUS] || undefined,
|
||||
filters[FILTER_KEYS.MODEL] || undefined
|
||||
filters[FILTER_KEYS.MODEL] || undefined,
|
||||
filters[FILTER_KEYS.KEY_ALIAS] || undefined
|
||||
);
|
||||
|
||||
if (currentTimestamp === lastSearchTimestamp.current && response.data) {
|
||||
@@ -123,6 +125,19 @@ export function useLogFilterLogic({
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Only do client-side filtering if no backend filters are active
|
||||
const hasBackendFilters =
|
||||
filters[FILTER_KEYS.KEY_ALIAS] ||
|
||||
filters[FILTER_KEYS.KEY_HASH] ||
|
||||
filters[FILTER_KEYS.REQUEST_ID] ||
|
||||
filters[FILTER_KEYS.USER_ID] ||
|
||||
filters[FILTER_KEYS.END_USER];
|
||||
|
||||
if (hasBackendFilters) {
|
||||
// Backend is handling filtering, don't override the results
|
||||
return;
|
||||
}
|
||||
|
||||
let filteredData = [...logs.data];
|
||||
|
||||
@@ -148,7 +163,7 @@ export function useLogFilterLogic({
|
||||
log => log.model === filters[FILTER_KEYS.MODEL]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (filters[FILTER_KEYS.KEY_HASH]) {
|
||||
filteredData = filteredData.filter(
|
||||
log => log.api_key === filters[FILTER_KEYS.KEY_HASH]
|
||||
@@ -161,24 +176,6 @@ export function useLogFilterLogic({
|
||||
);
|
||||
}
|
||||
|
||||
// Add key alias filtering
|
||||
if (filters[FILTER_KEYS.KEY_ALIAS]) {
|
||||
// We need to fetch the key info to get the key hash for the selected alias
|
||||
try {
|
||||
// Get the key hash for the selected alias
|
||||
const selectedKey = filters[FILTER_KEYS.KEY_ALIAS]
|
||||
|
||||
if (selectedKey) {
|
||||
// Filter logs by the key hash
|
||||
filteredData = filteredData.filter(
|
||||
log => log.metadata?.user_api_key_alias === selectedKey
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching key info for alias:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const newFilteredLogs: PaginatedResponse = {
|
||||
data: filteredData,
|
||||
total: logs.total,
|
||||
|
||||
Reference in New Issue
Block a user