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:
@@ -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 }}>
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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: {}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user