refactor(usage): switch breakdown dimension from model to route

Align with gpustack backend commits 7b57cd3b (add route dimension to
usage breakdown) and 51fd25c8 (use model_route_id as models_called):

* group_by, filters, and BreakdownItem now use the `route` dimension;
  `models` filter cascader is replaced with a flat `routes` selector
  (routes have no provider/cluster grouping).
* Tab / group_by option / column header keep the user-facing "模型 / Model"
  wording — only the underlying data dimension changed.
* Drop cluster_name / provider_type columns from the breakdown table;
  drop api_keys_used column per design.
* Fix pre-existing excel export bug in use-export-table — string
  dataIndex was lodash-joined char-by-char ("input_tokens" →
  "i_n_p_u_t..."), array dataIndex was used as a flat key against nested
  rows. Rebuilt around buildSheetMeta that emits a stable field key plus
  a formatMap using _.get(row, path) for nested values. Sheet name kept
  as `models` to match the visible column label.
This commit is contained in:
Yuxing Deng
2026-05-19 16:50:42 +08:00
parent 0274bcb8cd
commit 38107a67fe
15 changed files with 135 additions and 254 deletions
+2 -2
View File
@@ -34,7 +34,7 @@ export async function queryUsageTimeSeriesData(
perPage?: number; perPage?: number;
sort_by?: string; sort_by?: string;
filters: { filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
@@ -51,7 +51,7 @@ export async function queryUsageTimeSeriesData(
export async function queryUsageBreakdownList( export async function queryUsageBreakdownList(
params: Global.SearchParams & { params: Global.SearchParams & {
filters: { filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
@@ -1,14 +1,12 @@
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { GroupOption } from '../config';
import { UsageFilterItem } from '../config/types'; import { UsageFilterItem } from '../config/types';
import ApiKeysTable from '../tables/apikeys-table'; import ApiKeysTable from '../tables/apikeys-table';
import ModelsTable from '../tables/models-table'; import ModelsTable from '../tables/models-table';
import UsersTable from '../tables/users-table'; import UsersTable from '../tables/users-table';
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>; type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
type GroupOptionType = GroupOption<UsageFilterItem>;
const EMPTY_FILTERS: FilterOptionType[] = []; const EMPTY_FILTERS: FilterOptionType[] = [];
const BreakdownTabs: React.FC<{ const BreakdownTabs: React.FC<{
@@ -20,13 +18,13 @@ const BreakdownTabs: React.FC<{
pageResetKey?: number; pageResetKey?: number;
refreshKey?: number; refreshKey?: number;
filters: { filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => { }> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const models = filters.models || EMPTY_FILTERS; const routes = filters.routes || EMPTY_FILTERS;
const users = filters.users || EMPTY_FILTERS; const users = filters.users || EMPTY_FILTERS;
const apiKeys = filters.api_keys || EMPTY_FILTERS; const apiKeys = filters.api_keys || EMPTY_FILTERS;
@@ -39,7 +37,7 @@ const BreakdownTabs: React.FC<{
children: ( children: (
<ModelsTable <ModelsTable
key="models" key="models"
models={models} routes={routes}
dateRange={dateRange} dateRange={dateRange}
scope={scope} scope={scope}
pageResetKey={pageResetKey} pageResetKey={pageResetKey}
@@ -83,7 +81,7 @@ const BreakdownTabs: React.FC<{
} }
return true; return true;
}); });
}, [apiKeys, dateRange, models, pageResetKey, refreshKey, scope, users]); }, [apiKeys, dateRange, routes, pageResetKey, refreshKey, scope, users]);
return ( return (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
+1 -1
View File
@@ -104,7 +104,7 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
}; };
} }
const groupDim = groupBy as 'user' | 'model' | 'api_key' | null; const groupDim = groupBy as 'user' | 'route' | 'api_key' | null;
const isCached = metric === CACHED_METRIC; const isCached = metric === CACHED_METRIC;
const dateSet = new Set<string>( const dateSet = new Set<string>(
+9 -24
View File
@@ -22,7 +22,7 @@ const ExportData: React.FC<{
metaData: any; metaData: any;
granularity: string; granularity: string;
initialState: { initialState: {
activeModels: ValueType[][]; activeRoutes: string[];
activeApiKeys: ValueType[][]; activeApiKeys: ValueType[][];
users: string[]; users: string[];
start_date: string; start_date: string;
@@ -32,7 +32,7 @@ const ExportData: React.FC<{
scope: string; scope: string;
start_date: string; start_date: string;
end_date: string; end_date: string;
models: string[]; routes: string[];
users: string[]; users: string[];
api_keys: string[]; api_keys: string[];
}; };
@@ -84,7 +84,7 @@ const ExportData: React.FC<{
...pageParams, ...pageParams,
granularity: 'day', granularity: 'day',
sort_by: '-date', sort_by: '-date',
group_by: ['date', 'user', 'model', 'api_key'], group_by: ['date', 'user', 'route', 'api_key'],
filters: nextFilters, filters: nextFilters,
scope: initialScope, scope: initialScope,
start_date: nextCommonFilters.start_date, start_date: nextCommonFilters.start_date,
@@ -113,25 +113,12 @@ const ExportData: React.FC<{
} }
}, },
{ {
title: intl.formatMessage({ id: 'usage.table.cluster' }), title: intl.formatMessage({ id: 'usage.filter.group.model' }),
dataIndex: ['model', 'identity', 'value', 'cluster_name'], dataIndex: ['route', 'label'],
render: (text: string) => { render: (text: string) => {
return <AutoTooltip ghost>{text}</AutoTooltip>; return <AutoTooltip ghost>{text}</AutoTooltip>;
} }
}, },
{
title: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
dataIndex: ['model', 'identity', 'value', 'model_name'],
render: (text: string, record: any) => {
return (
<AutoTooltip ghost>
{record?.model?.identity?.value?.provider_name
? `${record?.model?.identity?.value?.provider_name}/${text}`
: text}
</AutoTooltip>
);
}
},
{ {
title: intl.formatMessage({ id: 'dashboard.usage.export.user' }), title: intl.formatMessage({ id: 'dashboard.usage.export.user' }),
dataIndex: ['user', 'label'], dataIndex: ['user', 'label'],
@@ -213,8 +200,7 @@ const ExportData: React.FC<{
jsonData: (dataSource.dataList || []).map((item: any) => ({ jsonData: (dataSource.dataList || []).map((item: any) => ({
date: item?.date?.label, date: item?.date?.label,
user: item?.user?.label, user: item?.user?.label,
cluster: item?.model?.identity?.value?.cluster_name, route: item?.route?.label,
model: item?.model?.identity?.value?.model_name,
api_key: item?.api_key?.label, api_key: item?.api_key?.label,
input_tokens: item?.input_tokens, input_tokens: item?.input_tokens,
input_cached_tokens: item?.input_cached_tokens, input_cached_tokens: item?.input_cached_tokens,
@@ -226,7 +212,7 @@ const ExportData: React.FC<{
fields: [ fields: [
'date', 'date',
'user', 'user',
'model', 'route',
'api_key', 'api_key',
'input_tokens', 'input_tokens',
'input_cached_tokens', 'input_cached_tokens',
@@ -237,8 +223,7 @@ const ExportData: React.FC<{
fieldLabels: { fieldLabels: {
date: intl.formatMessage({ id: 'dashboard.usage.export.date' }), date: intl.formatMessage({ id: 'dashboard.usage.export.date' }),
user: intl.formatMessage({ id: 'dashboard.usage.export.user' }), user: intl.formatMessage({ id: 'dashboard.usage.export.user' }),
cluster: intl.formatMessage({ id: 'usage.table.cluster' }), route: intl.formatMessage({ id: 'usage.filter.group.model' }),
model: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
api_key: intl.formatMessage({ id: 'usage.table.provider' }), api_key: intl.formatMessage({ id: 'usage.table.provider' }),
input_tokens: intl.formatMessage({ input_tokens: intl.formatMessage({
id: 'usage.filter.inputTokens' id: 'usage.filter.inputTokens'
@@ -286,7 +271,7 @@ const ExportData: React.FC<{
fetchExportData({ fetchExportData({
...INITIAL_PAGE_PARAMS, ...INITIAL_PAGE_PARAMS,
granularity: 'day', granularity: 'day',
group_by: ['date', 'user', 'model', 'api_key'], group_by: ['date', 'user', 'route', 'api_key'],
filters, filters,
sort_by: '-date', sort_by: '-date',
scope: initialScope, scope: initialScope,
+19 -111
View File
@@ -1,5 +1,4 @@
import useRangePickerPreset from '@/pages/dashboard/hooks/use-rangepicker-preset'; import useRangePickerPreset from '@/pages/dashboard/hooks/use-rangepicker-preset';
import ProviderLogo from '@/pages/maas-provider/components/provider-logo';
import { useModel } from '@@/plugin-model'; import { useModel } from '@@/plugin-model';
import { DownloadOutlined, SyncOutlined } from '@ant-design/icons'; import { DownloadOutlined, SyncOutlined } from '@ant-design/icons';
import { import {
@@ -32,21 +31,19 @@ interface FilterBarProps {
scope: string; scope: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
selectedModels: string[]; selectedRoutes: string[];
selectedUsers: string[]; selectedUsers: string[];
selectedApiKeys: string[]; selectedApiKeys: string[];
modelOptions: GroupOption<UsageFilterItem>[]; routeOptions: OptionType[];
userOptions: OptionType[]; userOptions: OptionType[];
apiKeyOptions: GroupOption<UsageFilterItem>[]; apiKeyOptions: GroupOption<UsageFilterItem>[];
activeModels: valueType[][];
activeApiKeys: valueType[][]; activeApiKeys: valueType[][];
handlePickerChange: (picker: DateType) => void; handlePickerChange: (picker: DateType) => void;
onScopeChange: (value: string) => void; onScopeChange: (value: string) => void;
onDateChange: (dates: any, dateStrings: [string, string]) => void; onDateChange: (dates: any, dateStrings: [string, string]) => void;
onModelsChange: (value: string[]) => void; onRoutesChange: (value: string[]) => void;
onUsersChange: (value: string[]) => void; onUsersChange: (value: string[]) => void;
onApiKeysChange: (value: string[]) => void; onApiKeysChange: (value: string[]) => void;
handleActiveModelsChange: (value: valueType[][]) => void;
handleActiveApiKeysChange: (value: valueType[][]) => void; handleActiveApiKeysChange: (value: valueType[][]) => void;
onExport?: () => void; onExport?: () => void;
handleSearch?: () => void; handleSearch?: () => void;
@@ -56,7 +53,7 @@ interface FilterBarProps {
scope: string; scope: string;
start_date: string; start_date: string;
end_date: string; end_date: string;
models: string[]; routes: string[];
users: string[]; users: string[];
api_keys: string[]; api_keys: string[];
}; };
@@ -67,18 +64,17 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
pageType = 'page', pageType = 'page',
startDate, startDate,
endDate, endDate,
selectedRoutes,
selectedUsers, selectedUsers,
selectedApiKeys, selectedApiKeys,
modelOptions, routeOptions,
userOptions, userOptions,
apiKeyOptions, apiKeyOptions,
activeModels,
activeApiKeys, activeApiKeys,
handleActiveModelsChange,
handleActiveApiKeysChange, handleActiveApiKeysChange,
handlePickerChange, handlePickerChange,
onDateChange, onDateChange,
onModelsChange, onRoutesChange,
onUsersChange, onUsersChange,
onApiKeysChange, onApiKeysChange,
onExportChart, onExportChart,
@@ -157,17 +153,6 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
); );
}; };
const handleOnModelsChange = (value: valueType[][], selectedOptions: any) => {
const selectedValues: string[] = value.map((item) => {
if (Array.isArray(item)) {
return item[item.length - 1] as string; // Get the last value in the array
}
return item as string;
});
handleActiveModelsChange(value);
onModelsChange(selectedValues);
};
const handleOnApiKeysChange = ( const handleOnApiKeysChange = (
value: valueType[][], value: valueType[][],
selectedOptions: any selectedOptions: any
@@ -182,55 +167,6 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
onApiKeysChange(selectedValues); onApiKeysChange(selectedValues);
}; };
const displayRender = (labels: any[], option: any) => {
return (
<AutoTooltip
ghost
maxWidth={150}
title={
<span>
{labels[0]} / {labels[1]}
</span>
}
>
{labels[0]} / {labels[1]}
</AutoTooltip>
);
};
const optionRender = (option: any) => {
const { data } = option;
if (!data.isParent) {
return (
<span className="flex-center gap-4">
<AutoTooltip ghost>{data.label}</AutoTooltip>
{data.deleted &&
renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))}
</span>
);
}
if (data.type === 'deployments') {
return (
<span className={FilterBarCss.optionsWrapper}>
<ProviderLogo provider={data.type as string} />
<AutoTooltip ghost>
{intl.formatMessage({ id: 'menu.models.deployment' })}
</AutoTooltip>
</span>
);
}
return (
<span className={FilterBarCss.optionsWrapper}>
<ProviderLogo provider={data.type as string} />
<AutoTooltip ghost>
<span>{data.label}</span>
</AutoTooltip>
</span>
);
};
const apiKeyOptionRender = (option: any) => { const apiKeyOptionRender = (option: any) => {
const { data } = option; const { data } = option;
if (!data.isParent) { if (!data.isParent) {
@@ -303,47 +239,19 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
style={{ width: 240 }} style={{ width: 240 }}
onChange={onDateChange} onChange={onDateChange}
/> />
<div <SimpleSelect
style={{ allowClear
maxWidth: 400, showSearch
flex: 1, mode="multiple"
minWidth: 200 options={routeOptions}
placeholder={intl.formatMessage({ id: 'usage.filter.model' })}
styles={{
wrapper: { flex: 1, maxWidth: 400, minWidth: 200 }
}} }}
> value={selectedRoutes}
<Cascader optionLabelRender={singleOptionRender}
showSearch onChange={onRoutesChange}
multiple={true} />
classNames={{
popup: {
root: 'cascader-popup-wrapper gpu-selector'
}
}}
styles={{
root: {
width: '100%'
},
popup: {
list: {
flex: 1
},
listItem: {
padding: '5px 10px'
}
}
}}
maxTagCount={1}
size="small"
isInFormItems={false}
placeholder={intl.formatMessage({ id: 'usage.filter.model' })}
options={modelOptions}
showCheckedStrategy="SHOW_CHILD"
displayRender={displayRender}
optionNode={optionRender}
value={activeModels}
onChange={handleOnModelsChange}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
></Cascader>
</div>
{initialState?.currentUser?.is_admin && ( {initialState?.currentUser?.is_admin && (
<> <>
<SimpleSelect <SimpleSelect
+1 -1
View File
@@ -6,7 +6,7 @@ export const groupByOptions = [
// label: 'None' // label: 'None'
// }, // },
{ {
value: 'model', value: 'route',
label: 'usage.filter.group.model' label: 'usage.filter.group.model'
}, },
{ {
+4
View File
@@ -9,11 +9,13 @@ export interface UsageFilterItem {
api_key_is_custom: boolean | null; api_key_is_custom: boolean | null;
provider_type: string | null; provider_type: string | null;
provider_name: string | null; provider_name: string | null;
route_name: string | null;
}; };
current: { current: {
model_id: string | null; model_id: string | null;
user_id: number | null; user_id: number | null;
api_key_id: string | null; api_key_id: string | null;
route_id: number | null;
}; };
}; };
label: string; label: string;
@@ -63,6 +65,7 @@ export type BreakdownItem = {
provider_name: string; provider_name: string;
user: UsageFilterItem; user: UsageFilterItem;
model: UsageFilterItem; model: UsageFilterItem;
route: UsageFilterItem;
api_key: UsageFilterItem; api_key: UsageFilterItem;
date: { date: {
value: string; value: string;
@@ -88,6 +91,7 @@ export interface UsageMeta {
models: UsageFilterItem[]; models: UsageFilterItem[];
users: UsageFilterItem[]; users: UsageFilterItem[];
api_keys: UsageFilterItem[]; api_keys: UsageFilterItem[];
routes: UsageFilterItem[];
}; };
} }
+42 -33
View File
@@ -10,6 +10,34 @@ import useAPIKeysColumns from './use-apikeys-columns';
import useModelsColumns from './use-models-columns'; import useModelsColumns from './use-models-columns';
import useUsersColumns from './use-users-columns'; import useUsersColumns from './use-users-columns';
type ColumnLike = {
title: any;
dataIndex: string | string[];
key?: string;
};
const toFieldKey = (dataIndex: string | string[]): string =>
Array.isArray(dataIndex) ? dataIndex.join('_') : dataIndex;
const buildSheetMeta = (columns: ColumnLike[]) => {
const fields: string[] = [];
const fieldLabels: Record<string, any> = {};
const formatMap: Record<string, (raw: any, row: any) => any> = {};
columns.forEach((col) => {
if (!col.dataIndex) return;
const key = toFieldKey(col.dataIndex);
fields.push(key);
fieldLabels[key] = col.title;
if (Array.isArray(col.dataIndex)) {
const path = col.dataIndex;
formatMap[key] = (_raw, row) => _.get(row, path);
}
});
return { fields, fieldLabels, formatMap };
};
const useExportTable = () => { const useExportTable = () => {
const store = useStore(); const store = useStore();
const modelsColumns = useModelsColumns(); const modelsColumns = useModelsColumns();
@@ -20,53 +48,34 @@ const useExportTable = () => {
const usersTableData = store.get(usersTableDataAtom); const usersTableData = store.get(usersTableDataAtom);
const apiKeysTableData = store.get(apiKeysTableDataAtom); const apiKeysTableData = store.get(apiKeysTableDataAtom);
const modelsTableData = store.get(modelsTableDataAtom); const modelsTableData = store.get(modelsTableDataAtom);
const modelsMeta = buildSheetMeta(modelsColumns);
const apiKeysMeta = buildSheetMeta(apiKeysColumns);
const usersMeta = buildSheetMeta(usersColumns);
exportJsonToExcel({ exportJsonToExcel({
fileName: 'table_data.xlsx', fileName: 'table_data.xlsx',
sheets: [ sheets: [
{ {
jsonData: modelsTableData.dataList || [], jsonData: modelsTableData.dataList || [],
sheetName: 'models', sheetName: 'models',
fields: modelsColumns fields: modelsMeta.fields,
.map((col: any) => _.join(col.dataIndex, '_')) fieldLabels: modelsMeta.fieldLabels,
.filter(Boolean) as string[], formatMap: modelsMeta.formatMap
fieldLabels: modelsColumns.reduce(
(map, col) => {
map[_.join(col.dataIndex, '_')] = col.title;
return map;
},
{} as Record<string, any>
),
formatMap: {}
}, },
{ {
jsonData: apiKeysTableData.dataList || [], jsonData: apiKeysTableData.dataList || [],
sheetName: 'api_keys', sheetName: 'api_keys',
fields: apiKeysColumns fields: apiKeysMeta.fields,
.map((col: any) => col.dataIndex) fieldLabels: apiKeysMeta.fieldLabels,
.filter(Boolean) as string[], formatMap: apiKeysMeta.formatMap
fieldLabels: apiKeysColumns.reduce(
(map, col) => {
map[_.join(col.dataIndex, '_')] = col.title;
return map;
},
{} as Record<string, any>
),
formatMap: {}
}, },
{ {
jsonData: usersTableData.dataList || [], jsonData: usersTableData.dataList || [],
sheetName: 'users', sheetName: 'users',
fields: usersColumns fields: usersMeta.fields,
.map((col: any) => col.dataIndex) fieldLabels: usersMeta.fieldLabels,
.filter(Boolean) as string[], formatMap: usersMeta.formatMap
fieldLabels: usersColumns.reduce(
(map, col) => {
map[_.join(col.dataIndex, '_')] = col.title;
return map;
},
{} as Record<string, any>
),
formatMap: {}
} }
] ]
}); });
+3 -30
View File
@@ -1,15 +1,10 @@
// columns.ts // columns.ts
import ProviderLogo from '@/pages/maas-provider/components/provider-logo';
import { AutoTooltip } from '@gpustack/core-ui'; import { AutoTooltip } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tag } from 'antd'; import { Tag } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { BreakdownItem as ListItem } from '../config/types'; import { BreakdownItem as ListItem } from '../config/types';
interface ColumnsHookProps {
sortOrder: string[];
}
const useModelsColumns = (): Array<{ const useModelsColumns = (): Array<{
title: string; title: string;
dataIndex: string | string[]; dataIndex: string | string[];
@@ -21,8 +16,8 @@ const useModelsColumns = (): Array<{
return [ return [
{ {
title: intl.formatMessage({ id: 'common.table.name' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: ['model', 'identity', 'value', 'model_name'], dataIndex: ['route', 'label'],
key: 'model_name', key: 'route_name',
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<span className="flex items-center"> <span className="flex items-center">
<AutoTooltip <AutoTooltip
@@ -32,7 +27,7 @@ const useModelsColumns = (): Array<{
> >
<span className="text-primary">{text}</span> <span className="text-primary">{text}</span>
</AutoTooltip> </AutoTooltip>
{record.model?.deleted && ( {record.route?.deleted && (
<Tag <Tag
style={{ style={{
marginLeft: 8, marginLeft: 8,
@@ -49,28 +44,6 @@ const useModelsColumns = (): Array<{
</span> </span>
) )
}, },
{
title: intl.formatMessage({ id: 'usage.table.cluster' }),
dataIndex: ['model', 'identity', 'value', 'cluster_name'],
key: 'cluster_name',
render: (text: string, record: ListItem) => (
<AutoTooltip ghost>{text || '-'}</AutoTooltip>
)
},
{
title: intl.formatMessage({ id: 'usage.table.provider' }),
dataIndex: ['model', 'identity', 'value', 'provider_type'],
key: 'provider_type',
render: (text: string, record: ListItem) => (
<span className="flex-center gap-8">
<ProviderLogo provider={text || 'deployments'} />
<AutoTooltip ghost style={{ maxWidth: 400 }}>
{text ||
(!text && intl.formatMessage({ id: 'menu.models.deployment' }))}
</AutoTooltip>
</span>
)
},
{ {
title: intl.formatMessage({ id: 'usage.filter.inputTokens' }), title: intl.formatMessage({ id: 'usage.filter.inputTokens' }),
dataIndex: 'input_tokens', dataIndex: 'input_tokens',
+23 -30
View File
@@ -12,6 +12,9 @@ const DefaultDateConfig = {
type UserOptionType = UsageFilterItem & { type UserOptionType = UsageFilterItem & {
value: string; value: string;
}; };
type RouteOptionType = UsageFilterItem & {
value: string;
};
type ValueType = string | number | null; type ValueType = string | number | null;
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>; type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
@@ -23,6 +26,7 @@ interface UseUsageFiltersParams {
models?: GroupOptionType[]; models?: GroupOptionType[];
users?: UserOptionType[]; users?: UserOptionType[];
api_keys?: GroupOptionType[]; api_keys?: GroupOptionType[];
routes?: RouteOptionType[];
}; };
chartFilters: { chartFilters: {
metric: string; metric: string;
@@ -30,7 +34,7 @@ interface UseUsageFiltersParams {
granularity: string; granularity: string;
}; };
initialState?: { initialState?: {
activeModels?: ValueType[][]; activeRoutes?: string[];
activeApiKeys?: ValueType[][]; activeApiKeys?: ValueType[][];
users?: string[]; users?: string[];
start_date?: string; start_date?: string;
@@ -45,13 +49,13 @@ interface UseUsageFiltersParams {
onFetchData?: (params: { onFetchData?: (params: {
chartFilters: UseUsageFiltersParams['chartFilters']; chartFilters: UseUsageFiltersParams['chartFilters'];
filters: { filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
commonFilters: { commonFilters: {
scope: string; scope: string;
models: string[]; routes: string[];
users: string[]; users: string[];
api_keys: string[]; api_keys: string[];
start_date: string; start_date: string;
@@ -70,7 +74,7 @@ export const useUsageFilters = ({
onFetchData onFetchData
}: UseUsageFiltersParams) => { }: UseUsageFiltersParams) => {
const { const {
activeModels: initialActiveModels = [], activeRoutes: initialActiveRoutes = [],
activeApiKeys: initialActiveApiKeys = [], activeApiKeys: initialActiveApiKeys = [],
users: initialUsers = [], users: initialUsers = [],
start_date = '', start_date = '',
@@ -93,7 +97,7 @@ export const useUsageFilters = ({
const [commonFilters, setCommonFilters] = useState({ const [commonFilters, setCommonFilters] = useState({
scope: initialScope, scope: initialScope,
models: extractSelectedValues(initialActiveModels), routes: initialActiveRoutes,
users: initialUsers || [], users: initialUsers || [],
api_keys: extractSelectedValues(initialActiveApiKeys), api_keys: extractSelectedValues(initialActiveApiKeys),
start_date: start_date:
@@ -104,11 +108,9 @@ export const useUsageFilters = ({
end_date: end_date || dayjs().format('YYYY-MM-DD') end_date: end_date || dayjs().format('YYYY-MM-DD')
}); });
const modelOptions = metaData?.models || []; const routeOptions = metaData?.routes || [];
const userOptions = metaData?.users || []; const userOptions = metaData?.users || [];
const apiKeyOptions = metaData?.api_keys || []; const apiKeyOptions = metaData?.api_keys || [];
const [activeModels, setActiveModels] =
useState<ValueType[][]>(initialActiveModels);
const [activeApiKeys, setActiveApiKeys] = const [activeApiKeys, setActiveApiKeys] =
useState<ValueType[][]>(initialActiveApiKeys); useState<ValueType[][]>(initialActiveApiKeys);
@@ -117,12 +119,11 @@ export const useUsageFilters = ({
return; return;
} }
setActiveModels(initialActiveModels);
setActiveApiKeys(initialActiveApiKeys); setActiveApiKeys(initialActiveApiKeys);
setCommonFilters((prev) => ({ setCommonFilters((prev) => ({
...prev, ...prev,
scope: initialScope, scope: initialScope,
models: extractSelectedValues(initialActiveModels), routes: initialActiveRoutes,
users: initialUsers || [], users: initialUsers || [],
api_keys: extractSelectedValues(initialActiveApiKeys), api_keys: extractSelectedValues(initialActiveApiKeys),
start_date: start_date:
@@ -135,7 +136,7 @@ export const useUsageFilters = ({
}, [ }, [
end_date, end_date,
initialActiveApiKeys, initialActiveApiKeys,
initialActiveModels, initialActiveRoutes,
initialUsers, initialUsers,
initialScope, initialScope,
initialState, initialState,
@@ -144,17 +145,15 @@ export const useUsageFilters = ({
const buildFilters = (selected: typeof commonFilters) => { const buildFilters = (selected: typeof commonFilters) => {
const filters: { const filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
} = {}; } = {};
if (selected.models.length > 0) { if (selected.routes.length > 0) {
const modelsSet = new Set(selected.models); const routesSet = new Set(selected.routes);
filters.models = modelOptions filters.routes = routeOptions
.flatMap((group) => .filter((item) => routesSet.has(item.value))
group.children.filter((item) => modelsSet.has(item.value))
)
.map((item) => ({ .map((item) => ({
identity: item.identity identity: item.identity
})); }));
@@ -184,7 +183,7 @@ export const useUsageFilters = ({
const filters = useMemo( const filters = useMemo(
() => buildFilters(commonFilters), () => buildFilters(commonFilters),
[commonFilters, modelOptions, userOptions, apiKeyOptions] [commonFilters, routeOptions, userOptions, apiKeyOptions]
); );
const fetchData = ( const fetchData = (
@@ -233,16 +232,12 @@ export const useUsageFilters = ({
} }
}; };
const handleActiveModelsChange = (value: ValueType[][]) => {
setActiveModels(value);
};
const handleActiveApiKeysChange = (value: ValueType[][]) => { const handleActiveApiKeysChange = (value: ValueType[][]) => {
setActiveApiKeys(value); setActiveApiKeys(value);
}; };
const handleFilterChange = ( const handleFilterChange = (
type: 'models' | 'users' | 'api_keys', type: 'routes' | 'users' | 'api_keys',
value: string[] value: string[]
) => { ) => {
const selectedValues: string[] = value.map((item) => { const selectedValues: string[] = value.map((item) => {
@@ -267,7 +262,7 @@ export const useUsageFilters = ({
const items = timeSeriesData?.items || []; const items = timeSeriesData?.items || [];
const groupDim = chartFilters.group_by as const groupDim = chartFilters.group_by as
| 'user' | 'user'
| 'model' | 'route'
| 'api_key' | 'api_key'
| null; | null;
const metric = chartFilters.metric as keyof BreakdownItem; const metric = chartFilters.metric as keyof BreakdownItem;
@@ -327,20 +322,18 @@ export const useUsageFilters = ({
scope: commonFilters.scope, scope: commonFilters.scope,
startDate: commonFilters.start_date, startDate: commonFilters.start_date,
endDate: commonFilters.end_date, endDate: commonFilters.end_date,
selectedModels: commonFilters.models, selectedRoutes: commonFilters.routes,
selectedUsers: commonFilters.users, selectedUsers: commonFilters.users,
selectedApiKeys: commonFilters.api_keys, selectedApiKeys: commonFilters.api_keys,
modelOptions, routeOptions,
userOptions, userOptions,
apiKeyOptions, apiKeyOptions,
activeApiKeys, activeApiKeys,
activeModels,
handleSearch, handleSearch,
handleActiveModelsChange,
handleActiveApiKeysChange, handleActiveApiKeysChange,
onScopeChange: handleScopeChange, onScopeChange: handleScopeChange,
onDateChange: handleDateChange, onDateChange: handleDateChange,
onModelsChange: (value: string[]) => handleFilterChange('models', value), onRoutesChange: (value: string[]) => handleFilterChange('routes', value),
onUsersChange: (value: string[]) => handleFilterChange('users', value), onUsersChange: (value: string[]) => handleFilterChange('users', value),
onApiKeysChange: (value: string[]) => handleFilterChange('api_keys', value), onApiKeysChange: (value: string[]) => handleFilterChange('api_keys', value),
onExportChart: handleOnExportChart onExportChart: handleOnExportChart
+3 -3
View File
@@ -164,8 +164,8 @@ const Usage: React.FC = () => {
filterBar.onDateChange(dates, dateStrings); filterBar.onDateChange(dates, dateStrings);
handleBreakdownPageReset(); handleBreakdownPageReset();
}} }}
onModelsChange={(value) => { onRoutesChange={(value) => {
filterBar.onModelsChange(value); filterBar.onRoutesChange(value);
handleBreakdownPageReset(); handleBreakdownPageReset();
}} }}
onUsersChange={(value) => { onUsersChange={(value) => {
@@ -228,7 +228,7 @@ const Usage: React.FC = () => {
handlePickerChange={handlePickerChange} handlePickerChange={handlePickerChange}
onCancel={() => setOpenExportModal(false)} onCancel={() => setOpenExportModal(false)}
initialState={{ initialState={{
activeModels: filterBar.activeModels, activeRoutes: filterBar.selectedRoutes,
activeApiKeys: filterBar.activeApiKeys, activeApiKeys: filterBar.activeApiKeys,
users: commonFilters.users, users: commonFilters.users,
start_date: commonFilters.start_date, start_date: commonFilters.start_date,
@@ -31,7 +31,7 @@ export const useQueryBreakdownList = (options?: {
ListItem, ListItem,
Global.SearchParams & { Global.SearchParams & {
filters: { filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
@@ -46,7 +46,7 @@ export const useQueryBreakdownList = (options?: {
const fetchListData = async ( const fetchListData = async (
params: Global.SearchParams & { params: Global.SearchParams & {
filters: { filters: {
models?: FilterOptionType[]; routes?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
@@ -9,6 +9,10 @@ type UserOptionType = UsageFilterItem & {
value: string; value: string;
}; };
type RouteOptionType = UsageFilterItem & {
value: string;
};
export default function useQueryUsageMetaData() { export default function useQueryUsageMetaData() {
const { detailData, loading, cancelRequest, fetchData } = const { detailData, loading, cancelRequest, fetchData } =
useQueryData<UsageMeta>({ useQueryData<UsageMeta>({
@@ -20,10 +24,12 @@ export default function useQueryUsageMetaData() {
models: GroupOption<UsageFilterItem>[]; models: GroupOption<UsageFilterItem>[];
users: UserOptionType[]; users: UserOptionType[];
api_keys: GroupOption<UsageFilterItem>[]; api_keys: GroupOption<UsageFilterItem>[];
routes: RouteOptionType[];
}>({ }>({
models: [], models: [],
users: [], users: [],
api_keys: [] api_keys: [],
routes: []
}); });
// the current user sort in the first place // the current user sort in the first place
@@ -70,7 +76,12 @@ export default function useQueryUsageMetaData() {
value: item.label, value: item.label,
label: item.identity.value.api_key_name || '' label: item.identity.value.api_key_name || ''
}) })
}) }),
routes:
(res?.filters?.routes || []).map((item) => ({
...item,
value: item.label
})) || []
}; };
setResult(data); setResult(data);
}; };
+7 -7
View File
@@ -6,17 +6,17 @@ import { Table } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { BreakdownItem, FilterOptionType } from '../config/types'; import { BreakdownItem, FilterOptionType } from '../config/types';
import useUsersColumns from '../hooks/use-models-columns'; import useModelsColumns from '../hooks/use-models-columns';
import useQueryBreakdownList from '../services/use-query-breakdown-list'; import useQueryBreakdownList from '../services/use-query-breakdown-list';
import getBreakdownRowKey from '../utils/get-breakdown-row-key'; import getBreakdownRowKey from '../utils/get-breakdown-row-key';
const Models: React.FC<{ const Models: React.FC<{
models: FilterOptionType[]; routes: FilterOptionType[];
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
pageResetKey?: number; pageResetKey?: number;
refreshKey?: number; refreshKey?: number;
}> = ({ models, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => { }> = ({ routes, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -51,7 +51,7 @@ const Models: React.FC<{
})); }));
}; };
const columns = useUsersColumns(); const columns = useModelsColumns();
useEffect(() => { useEffect(() => {
if (queryParams.page !== 1) { if (queryParams.page !== 1) {
@@ -89,9 +89,9 @@ const Models: React.FC<{
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: ['model'], group_by: ['route'],
filters: { filters: {
models routes
}, },
scope: scope, scope: scope,
...dateRange ...dateRange
@@ -99,7 +99,7 @@ const Models: React.FC<{
}, [ }, [
dateRange.end_date, dateRange.end_date,
dateRange.start_date, dateRange.start_date,
models, routes,
queryParams.page, queryParams.page,
queryParams.perPage, queryParams.perPage,
queryParams.sort_by, queryParams.sort_by,
@@ -5,7 +5,7 @@ export const getBreakdownRowKey = (
type: string type: string
): string => { ): string => {
if (type === 'models') { if (type === 'models') {
return `${record.model.label}`; return `${record.route?.label || ''}`;
} }
if (type === 'users') { if (type === 'users') {
@@ -16,7 +16,7 @@ export const getBreakdownRowKey = (
return `${record.api_key.label}`; return `${record.api_key.label}`;
} }
return `${record.date?.value || ''}-${record.model.label}-${record.user.label}-${record.api_key.label}`; return `${record.date?.value || ''}-${record.route?.label || ''}-${record.user.label}-${record.api_key.label}`;
}; };
export default getBreakdownRowKey; export default getBreakdownRowKey;