style: card style

This commit is contained in:
jialin
2026-04-22 13:16:48 +08:00
committed by jialin
parent daa3d74500
commit c92f56ec8f
19 changed files with 532 additions and 222 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
border-radius: var(--border-radius-lg); border-radius: var(--border-radius-lg);
background-color: var(--ant-color-bg-container); background-color: var(--ant-color-bg-container);
box-shadow: none; box-shadow: none;
padding: 10px 16px; padding: 8px 16px;
border: 1px solid var(--ant-color-border); border: 1px solid var(--ant-color-border);
`; `;
+1 -1
View File
@@ -87,7 +87,7 @@ export const SimpleCard: React.FC<{
label: string; label: string;
value: React.ReactNode; value: React.ReactNode;
color: string; color: string;
iconType: string; iconType?: string;
}[]; }[];
height?: string | number; height?: string | number;
bordered?: boolean; bordered?: boolean;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
// import './iconfont/iconfont.js'; // import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4613488_htxf2x5ewb.js' scriptUrl: '//at.alicdn.com/t/c/font_4613488_mk9rqojjoqk.js'
}); });
export default IconFont; export default IconFont;
+1 -1
View File
@@ -169,7 +169,7 @@ body {
} }
} }
.ant-table-container .ant-table-content table { .ant-table-container table {
// border-spacing: 0 20px; // border-spacing: 0 20px;
.ant-table-thead th.ant-table-column-sort { .ant-table-thead th.ant-table-column-sort {
@@ -12,6 +12,8 @@ interface RangePickerPreset {
disabledDate?: boolean; disabledDate?: boolean;
} }
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
export default function useRangePickerPreset(options?: RangePickerPreset): { export default function useRangePickerPreset(options?: RangePickerPreset): {
disabledRangeDaysDate: DatePickerProps['disabledDate']; disabledRangeDaysDate: DatePickerProps['disabledDate'];
rangePresets: { rangePresets: {
@@ -21,19 +23,15 @@ export default function useRangePickerPreset(options?: RangePickerPreset): {
normalizeRangeValue: ( normalizeRangeValue: (
startDate: string, startDate: string,
endDate: string, endDate: string,
picker: 'date' | 'week' | 'month' | 'quarter' | 'year' picker: DateType
) => [Dayjs, Dayjs]; ) => [Dayjs, Dayjs];
handleOnPickerChange: ( handleOnPickerChange: (value: DateType) => void;
value: 'date' | 'week' | 'month' | 'quarter' | 'year' picker: DateType;
) => void;
picker: 'date' | 'week' | 'month' | 'quarter' | 'year';
range: number; range: number;
} { } {
const { range = 60, disabledDate, presetRanges } = options || {}; const { range = 60, disabledDate, presetRanges } = options || {};
const intl = useIntl(); const intl = useIntl();
const [picker, setPicker] = useState< const [picker, setPicker] = useState<DateType>('date');
'date' | 'week' | 'month' | 'quarter' | 'year'
>('date');
const getYearMonth = (date: Dayjs) => date.year() * 12 + date.month(); const getYearMonth = (date: Dayjs) => date.year() * 12 + date.month();
@@ -132,9 +130,7 @@ export default function useRangePickerPreset(options?: RangePickerPreset): {
return false; return false;
}; };
const handleOnPickerChange = ( const handleOnPickerChange = (value: DateType) => {
value: 'date' | 'week' | 'month' | 'quarter' | 'year'
) => {
setPicker(value); setPicker(value);
}; };
+59 -48
View File
@@ -1,5 +1,5 @@
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import React from 'react'; import React, { useMemo } from 'react';
import { GroupOption } from '../config'; 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';
@@ -8,6 +8,7 @@ import UsersTable from '../tables/users-table';
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>; type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
type GroupOptionType = GroupOption<UsageFilterItem>; type GroupOptionType = GroupOption<UsageFilterItem>;
const EMPTY_FILTERS: FilterOptionType[] = [];
const BreakdownTabs: React.FC<{ const BreakdownTabs: React.FC<{
dateRange: { dateRange: {
@@ -15,58 +16,68 @@ const BreakdownTabs: React.FC<{
end_date: string; end_date: string;
}; };
scope: string; scope: string;
refreshKey?: number;
filters: { filters: {
models?: FilterOptionType[]; models?: FilterOptionType[];
users?: FilterOptionType[]; users?: FilterOptionType[];
api_keys?: FilterOptionType[]; api_keys?: FilterOptionType[];
}; };
}> = ({ filters, dateRange, scope }) => { }> = ({ filters, dateRange, scope, refreshKey = 0 }) => {
const items = [ const models = filters.models || EMPTY_FILTERS;
{ const users = filters.users || EMPTY_FILTERS;
key: 'models', const apiKeys = filters.api_keys || EMPTY_FILTERS;
label: 'Models',
forceRender: true, const items = useMemo(() => {
children: ( return [
<ModelsTable {
key="models" key: 'models',
models={filters.models || []} label: 'Models',
dateRange={dateRange} forceRender: true,
scope={scope} children: (
/> <ModelsTable
) key="models"
}, models={models}
{ dateRange={dateRange}
key: 'users', scope={scope}
label: 'Users', refreshKey={refreshKey}
forceRender: true, />
children: ( )
<UsersTable },
key="users" {
users={filters.users || []} key: 'users',
dateRange={dateRange} label: 'Users',
scope={scope} forceRender: true,
/> children: (
) <UsersTable
}, key="users"
{ users={users}
key: 'api_keys', dateRange={dateRange}
label: 'API Keys', scope={scope}
forceRender: true, refreshKey={refreshKey}
children: ( />
<ApiKeysTable )
key="api_keys" },
apiKeys={filters.api_keys || []} {
dateRange={dateRange} key: 'api_keys',
scope={scope} label: 'API Keys',
/> forceRender: true,
) children: (
} <ApiKeysTable
].filter((item) => { key="api_keys"
if (item.key === 'users') { apiKeys={apiKeys}
return scope === 'all'; dateRange={dateRange}
} scope={scope}
return true; refreshKey={refreshKey}
}); />
)
}
].filter((item) => {
if (item.key === 'users') {
return scope === 'all';
}
return true;
});
}, [apiKeys, dateRange, models, refreshKey, scope, users]);
return ( return (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
+14 -61
View File
@@ -2,7 +2,6 @@ import CardWrapper from '@/components/card-wrapper';
import MixLineBar from '@/components/echarts/mix-line-bar'; import MixLineBar from '@/components/echarts/mix-line-bar';
import BaseSelect from '@/components/seal-form/base/select'; import BaseSelect from '@/components/seal-form/base/select';
import { baseColorMap } from '@/pages/dashboard/config'; import { baseColorMap } from '@/pages/dashboard/config';
import { formatLargeNumber } from '@/utils';
import { Segmented } from 'antd'; import { Segmented } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
@@ -31,10 +30,10 @@ const ControlLabel = styled.span`
interface DailyUsageProps { interface DailyUsageProps {
timeSeriesData: TimeSeriesData | null; timeSeriesData: TimeSeriesData | null;
metric: string; metric: string;
groupBy: string; groupBy: string | null;
granularity: string; granularity: string;
onMetricChange: (value: string) => void; onMetricChange: (value: string) => void;
onGroupByChange: (value: string) => void; onGroupByChange: (value: string | null) => void;
onGranularityChange: (value: string) => void; onGranularityChange: (value: string) => void;
} }
@@ -53,49 +52,6 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
onGranularityChange onGranularityChange
} = props; } = props;
const summary = timeSeriesData?.summary || {
input_tokens: 0,
output_tokens: 0,
total_tokens: 0,
api_requests: 0,
models_called: 0
};
const summaryCards = useMemo(() => {
return [
{
label: formatLargeNumber(summary.input_tokens) as string,
value: 'Input tokens',
color: baseColorMap.baseR3,
iconType: 'roundRect'
},
{
label: formatLargeNumber(summary.output_tokens) as string,
value: 'Output tokens',
color: baseColorMap.base,
iconType: 'roundRect'
},
{
label: formatLargeNumber(summary.total_tokens) as string,
value: 'Total tokens',
color: baseColorMap.baseL1,
iconType: 'roundRect'
},
{
label: formatLargeNumber(summary.api_requests) as string,
value: 'API requests',
color: baseColorMap.baseR1,
iconType: 'circle'
},
{
label: summary.models_called.toString(),
value: 'Models used',
color: baseColorMap.baseR2,
iconType: 'roundRect'
}
];
}, [summary]);
const { chartData, xAxisData } = useMemo(() => { const { chartData, xAxisData } = useMemo(() => {
if (!timeSeriesData?.series || timeSeriesData.series.length === 0) { if (!timeSeriesData?.series || timeSeriesData.series.length === 0) {
return { chartData: { line: [], bar: [] }, xAxisData: [] }; return { chartData: { line: [], bar: [] }, xAxisData: [] };
@@ -120,17 +76,10 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
})); }));
// API requests use line chart, tokens use bar chart // API requests use line chart, tokens use bar chart
if (metric === 'api_requests') { return {
return { chartData: { line: [], bar: chartSeries },
chartData: { line: chartSeries, bar: [] }, xAxisData: xAxis
xAxisData: xAxis };
};
} else {
return {
chartData: { line: [], bar: chartSeries },
xAxisData: xAxis
};
}
}, [timeSeriesData, metric]); }, [timeSeriesData, metric]);
const legendData = useMemo(() => { const legendData = useMemo(() => {
@@ -142,6 +91,10 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
})); }));
}, [timeSeriesData, metric]); }, [timeSeriesData, metric]);
const handleOnGroupByChange = (value: string) => {
onGroupByChange(value || null);
};
return ( return (
<div> <div>
<CardWrapper style={{ width: '100%', marginTop: 20 }}> <CardWrapper style={{ width: '100%', marginTop: 20 }}>
@@ -154,16 +107,18 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
value={metric} value={metric}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
onChange={onMetricChange} onChange={onMetricChange}
style={{ width: 180 }} style={{ width: 'max-content' }}
/> />
<BaseSelect <BaseSelect
allowClear
variant="borderless" variant="borderless"
prefix={<ControlLabel>Group by</ControlLabel>} prefix={<ControlLabel>Group by</ControlLabel>}
options={groupByOptions} options={groupByOptions}
value={groupBy} value={groupBy}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
onChange={onGroupByChange} onChange={handleOnGroupByChange}
style={{ width: 'max-content' }}
/> />
</div> </div>
<Segmented <Segmented
@@ -173,8 +128,6 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
onChange={onGranularityChange} onChange={onGranularityChange}
></Segmented> ></Segmented>
</ControlsWrapper> </ControlsWrapper>
{/* <SimpleCard dataList={summaryCards} height={80} /> */}
<MixLineBar <MixLineBar
chartData={chartData} chartData={chartData}
seriesData={[]} seriesData={[]}
+199 -31
View File
@@ -1,36 +1,88 @@
import AutoTooltip from '@/components/auto-tooltip'; import AutoTooltip from '@/components/auto-tooltip';
import ModalFooter from '@/components/modal-footer'; import ModalFooter from '@/components/modal-footer';
import ScrollerModal from '@/components/scroller-modal'; import ScrollerModal from '@/components/scroller-modal';
import { exportJsonToExcel } from '@/utils/excel-reader';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Table, TableColumnType } from 'antd'; import { Table, TableColumnType } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useUsageFilters } from '../hooks/use-usage-filters'; import { useUsageFilters } from '../hooks/use-usage-filters';
import useQueryBreakdownList from '../services/use-query-breakdown-list';
import getBreakdownRowKey from '../utils/get-breakdown-row-key';
import FilterBar from './filter-bar'; import FilterBar from './filter-bar';
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
type ValueType = string | number | null;
const ExportData: React.FC<{ const ExportData: React.FC<{
open: boolean; open: boolean;
onCancel: () => void; onCancel: () => void;
initialScope: string; initialScope: string;
metaData: any; metaData: any;
granularity: string;
initialState: {
activeModels: ValueType[][];
activeApiKeys: ValueType[][];
};
handlePickerChange: (picker: DateType) => void;
}> = (props) => { }> = (props) => {
const { open, onCancel, initialScope, metaData } = props || {}; const {
open,
onCancel,
initialScope,
metaData,
granularity,
handlePickerChange,
initialState
} = props || {};
const intl = useIntl(); const intl = useIntl();
const { const [pageParams, setPageParams] = React.useState<{
filters, page: number;
commonFilters, perPage: number;
fetchData, }>({
loading, page: 1,
timeSeriesData, perPage: 100
filterBar
} = useUsageFilters({
initialScope: initialScope,
metaData,
chartFilters: {},
summaryColumns: {}
}); });
const exportTableColumns: TableColumnType[] = [ const {
fetchData: fetchExportData,
loading,
dataSource,
cancelRequest
} = useQueryBreakdownList({
key: 'exportTableData'
});
const { filters, commonFilters, filterBar } = useUsageFilters({
initialScope: initialScope,
metaData,
chartFilters: {
metric: 'total_tokens',
group_by: null,
granularity: props.granularity || 'day'
},
initialState: initialState,
summaryColumns: [],
autoFetchOnFilterChange: true,
onFetchData: ({
chartFilters: nextChartFilters,
filters: nextFilters,
commonFilters: nextCommonFilters
}) => {
fetchExportData({
...pageParams,
granularity: nextChartFilters.granularity,
group_by: ['date', 'user', 'model', 'api_key'],
filters: nextFilters,
scope: initialScope,
start_date: nextCommonFilters.start_date,
end_date: nextCommonFilters.end_date
});
}
});
const exportTableColumns: TableColumnType<any>[] = [
{ {
title: intl.formatMessage({ id: 'resources.table.index' }), title: intl.formatMessage({ id: 'resources.table.index' }),
width: 80, width: 80,
@@ -40,34 +92,124 @@ const ExportData: React.FC<{
}, },
{ {
title: intl.formatMessage({ id: 'dashboard.usage.export.date' }), title: intl.formatMessage({ id: 'dashboard.usage.export.date' }),
dataIndex: 'date' dataIndex: ['date', 'label'],
render: (text: string) => {
return (
<AutoTooltip ghost>
<span className="text-primary">{text}</span>
</AutoTooltip>
);
}
}, },
{ {
title: intl.formatMessage({ id: 'dashboard.usage.export.user' }), title: 'Cluster',
dataIndex: 'user_name', dataIndex: ['model', 'identity', 'value', 'cluster_name'],
render: (text: string) => { render: (text: string) => {
return <AutoTooltip ghost>{text}</AutoTooltip>; return <AutoTooltip ghost>{text}</AutoTooltip>;
} }
}, },
{ {
title: intl.formatMessage({ id: 'dashboard.usage.export.model' }), title: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
dataIndex: 'model_id', 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' }),
dataIndex: ['user', 'label'],
render: (text: string) => {
return <AutoTooltip ghost>{text}</AutoTooltip>;
}
},
{
title: 'API Key',
dataIndex: ['api_key', 'label'],
render: (text: string, record: any) => { render: (text: string, record: any) => {
return <AutoTooltip ghost>{text}</AutoTooltip>; return <AutoTooltip ghost>{text}</AutoTooltip>;
} }
}, },
{ {
title: 'API Key', title: 'Input Tokens',
dataIndex: 'api_key', dataIndex: 'input_tokens'
render: (text: string, record: any) => { },
return <AutoTooltip ghost>{text}</AutoTooltip>; {
} title: 'Output Tokens',
dataIndex: 'output_tokens'
},
{
title: 'Total Tokens',
dataIndex: 'total_tokens'
},
{
title: 'API Requests',
dataIndex: 'api_requests'
} }
]; ];
const handleSubmit = () => { const handleSubmit = () => {
filterBar.onExportChart(); exportJsonToExcel({
fileName: `usage_export_${commonFilters.start_date}_${commonFilters.end_date}.xlsx`,
sheets: [
{
jsonData: (dataSource.dataList || []).map((item: any) => ({
date: item?.date?.label,
user: item?.user?.label,
cluster: item?.model?.identity?.value?.cluster_name,
model: item?.model?.identity?.value?.model_name,
api_key: item?.api_key?.label,
input_tokens: item?.input_tokens,
output_tokens: item?.output_tokens,
total_tokens: item?.total_tokens,
api_requests: item?.api_requests
})),
sheetName: 'usage',
fields: [
'date',
'user',
'model',
'api_key',
'input_tokens',
'output_tokens',
'total_tokens',
'api_requests'
],
fieldLabels: {
date: intl.formatMessage({ id: 'dashboard.usage.export.date' }),
user: intl.formatMessage({ id: 'dashboard.usage.export.user' }),
cluster: 'Cluster',
model: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
api_key: 'API Key',
input_tokens: 'Input Tokens',
output_tokens: 'Output Tokens',
total_tokens: 'Total Tokens',
api_requests: 'API Requests'
},
formatMap: {}
}
]
});
};
const handlePageChange = (page: number, pageSize: number) => {
setPageParams({ page, perPage: pageSize });
fetchExportData({
...pageParams,
page,
perPage: pageSize,
granularity,
group_by: ['date', 'user', 'model', 'api_key'],
filters,
scope: initialScope,
start_date: commonFilters.start_date,
end_date: commonFilters.end_date
});
}; };
const handleOnCancel = () => { const handleOnCancel = () => {
@@ -76,11 +218,22 @@ const ExportData: React.FC<{
useEffect(() => { useEffect(() => {
if (open) { if (open) {
fetchData(commonFilters, {}); fetchExportData({
...pageParams,
granularity,
group_by: ['date', 'user', 'model', 'api_key'],
filters,
scope: initialScope,
start_date: commonFilters.start_date,
end_date: commonFilters.end_date
});
} else { } else {
cancelRequest();
} }
}, [open]); }, [open]);
console.log('export dataSource', filterBar);
return ( return (
<ScrollerModal <ScrollerModal
title={intl.formatMessage({ id: 'dashboard.usage.export' })} title={intl.formatMessage({ id: 'dashboard.usage.export' })}
@@ -105,17 +258,32 @@ const ExportData: React.FC<{
></ModalFooter> ></ModalFooter>
} }
> >
<FilterBar {...filterBar} pageType="modal"></FilterBar> <FilterBar
{...filterBar}
pageType="modal"
handlePickerChange={handlePickerChange}
></FilterBar>
<Table <Table
columns={exportTableColumns} columns={exportTableColumns}
tableLayout={'auto'} tableLayout={'auto'}
style={{ width: '100%', marginTop: '16px', minHeight: 300 }} style={{ width: '100%', marginTop: '16px', minHeight: 300 }}
dataSource={timeSeriesData.series || []} dataSource={dataSource.dataList || []}
loading={loading} rowKey={getBreakdownRowKey}
rowKey="id" loading={{
spinning: loading,
size: 'middle'
}}
virtual virtual
scroll={{ y: 400 }} scroll={{ y: 400 }}
pagination={false} pagination={{
size: 'small',
pageSize: pageParams.perPage,
current: pageParams.page,
total: dataSource.total || 0,
onChange: handlePageChange,
hideOnSinglePage: pageParams.perPage === 100,
showSizeChanger: true
}}
></Table> ></Table>
</ScrollerModal> </ScrollerModal>
); );
+41 -16
View File
@@ -1,4 +1,5 @@
import AutoTooltip from '@/components/auto-tooltip'; import AutoTooltip from '@/components/auto-tooltip';
import IconFont from '@/components/icon-font';
import SealCascader from '@/components/seal-form/seal-cascader'; import SealCascader from '@/components/seal-form/seal-cascader';
import SimpleSelect from '@/components/seal-form/simple-select'; import SimpleSelect from '@/components/seal-form/simple-select';
import useRangePickerPreset from '@/pages/dashboard/hooks/use-rangepicker-preset'; import useRangePickerPreset from '@/pages/dashboard/hooks/use-rangepicker-preset';
@@ -21,6 +22,9 @@ const DefaultDateConfig = {
type OptionType = UsageFilterItem & { type OptionType = UsageFilterItem & {
value: string; value: string;
}; };
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
interface FilterBarProps { interface FilterBarProps {
pageType?: 'page' | 'modal'; pageType?: 'page' | 'modal';
scope: string; scope: string;
@@ -32,6 +36,7 @@ interface FilterBarProps {
modelOptions: GroupOption<UsageFilterItem>[]; modelOptions: GroupOption<UsageFilterItem>[];
userOptions: OptionType[]; userOptions: OptionType[];
apiKeyOptions: GroupOption<UsageFilterItem>[]; apiKeyOptions: GroupOption<UsageFilterItem>[];
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; onModelsChange: (value: string[]) => void;
@@ -53,6 +58,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
modelOptions, modelOptions,
userOptions, userOptions,
apiKeyOptions, apiKeyOptions,
handlePickerChange,
onDateChange, onDateChange,
onModelsChange, onModelsChange,
onUsersChange, onUsersChange,
@@ -107,12 +113,22 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
const exportMenuItems: MenuProps['items'] = [ const exportMenuItems: MenuProps['items'] = [
{ {
key: 'chart', key: 'chart',
label: 'Export Chart Data', label: (
<span className="flex-center gap-8">
<IconFont type="icon-chart-01" style={{ fontSize: 14 }} />
<span>Export Chart Data</span>
</span>
),
onClick: onExportChart onClick: onExportChart
}, },
{ {
key: 'table', key: 'table',
label: 'Export Table Data', label: (
<span className="flex-center gap-8">
<IconFont type="icon-table" style={{ fontSize: 14 }} />
<span>Export Table Data</span>
</span>
),
onClick: onExportTable onClick: onExportTable
} }
]; ];
@@ -187,6 +203,11 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
); );
}; };
const onPickerChange = (picker: DateType) => {
handleOnPickerChange(picker);
handlePickerChange(picker);
};
const renderFooter = () => { const renderFooter = () => {
return ( return (
<Segmented <Segmented
@@ -208,19 +229,19 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
flex: 1 flex: 1
} }
}} }}
onChange={handleOnPickerChange} onChange={onPickerChange}
options={[ options={[
{ {
label: 'Day', label: 'Day',
value: 'date' value: 'date'
}, },
{
label: 'Week',
value: 'week'
},
{ {
label: 'Month', label: 'Month',
value: 'month' value: 'month'
},
{
label: 'Week',
value: 'week'
} }
]} ]}
></Segmented> ></Segmented>
@@ -232,11 +253,18 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
return ( return (
<span className="flex-center gap-8"> <span className="flex-center gap-8">
<span>{data.label}</span> <span>{data.label}</span>
{data.isCurrent && <span className="text-tertiary"> [Current]</span>} {data.isCurrent && (
<span className="text-tertiary"> [Current Account]</span>
)}
</span> </span>
); );
}; };
// only for dates greater than the current day should be disabled.
const disabledDate = (current: dayjs.Dayjs) => {
return current && current > dayjs().endOf('day');
};
return ( return (
<div className={FilterBarCss.wrapper}> <div className={FilterBarCss.wrapper}>
<div className={FilterBarCss.filters}> <div className={FilterBarCss.filters}>
@@ -248,12 +276,11 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
]} ]}
format={'YYYY-MM-DD'} format={'YYYY-MM-DD'}
picker={picker} picker={picker}
disabledDate={disabledRangeDaysDate} disabledDate={disabledDate}
presets={rangePresets} presets={rangePresets}
allowClear={false} allowClear={false}
style={{ width: 240 }} style={{ width: 240 }}
onChange={onDateChange} onChange={onDateChange}
renderExtraFooter={renderFooter}
/> />
<div <div
style={{ style={{
@@ -285,9 +312,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
}} }}
maxTagCount={1} maxTagCount={1}
size="small" size="small"
placeholder={intl.formatMessage({ placeholder={'Filter by model'}
id: 'dashboard.usage.selectmodel'
})}
options={modelOptions} options={modelOptions}
showCheckedStrategy="SHOW_CHILD" showCheckedStrategy="SHOW_CHILD"
displayRender={displayRender} displayRender={displayRender}
@@ -304,7 +329,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
showSearch showSearch
mode="multiple" mode="multiple"
options={userOptions} options={userOptions}
placeholder="User" placeholder="Filter by user"
styles={{ styles={{
wrapper: { flex: 1, maxWidth: 240, minWidth: 150 } wrapper: { flex: 1, maxWidth: 240, minWidth: 150 }
}} }}
@@ -342,7 +367,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
}} }}
maxTagCount={1} maxTagCount={1}
size="small" size="small"
placeholder="API Key" placeholder="Filter by API key"
options={apiKeyOptions} options={apiKeyOptions}
showCheckedStrategy="SHOW_CHILD" showCheckedStrategy="SHOW_CHILD"
value={activeApiKeys} value={activeApiKeys}
@@ -359,7 +384,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
mode="multiple" mode="multiple"
options={apiKeyOptions?.[0]?.children || []} options={apiKeyOptions?.[0]?.children || []}
maxTagCount={0} maxTagCount={0}
placeholder="API Key" placeholder="Filter by API key"
styles={{ styles={{
wrapper: { flex: 1, maxWidth: 240, minWidth: 100 } wrapper: { flex: 1, maxWidth: 240, minWidth: 100 }
}} }}
+6 -5
View File
@@ -1,10 +1,10 @@
import { UsageFilterItem } from './types'; import { UsageFilterItem } from './types';
export const groupByOptions = [ export const groupByOptions = [
{ // {
value: '', // value: null,
label: 'None' // label: 'None'
}, // },
{ {
value: 'model', value: 'model',
label: 'Model' label: 'Model'
@@ -163,7 +163,8 @@ export function groupToOptions<T, TChild>(
}); });
} }
groupedMap.get(groupKey)!.children.push(options.getChild(item)); const child = options.getChild(item) as TChild & { value: string };
groupedMap.get(groupKey)!.children.push(child);
}); });
return Array.from(groupedMap.values()); return Array.from(groupedMap.values());
@@ -7,7 +7,7 @@ import { BreakdownItem as ListItem } from '../config/types';
const useModelsColumns = (): Array<{ const useModelsColumns = (): Array<{
title: string; title: string;
dataIndex: string; dataIndex: string | string[];
key: string; key: string;
}> => { }> => {
const intl = useIntl(); const intl = useIntl();
@@ -16,7 +16,7 @@ const useModelsColumns = (): Array<{
return [ return [
{ {
title: intl.formatMessage({ id: 'common.table.name' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'api_key_name', dataIndex: ['api_key', 'identity', 'value', 'api_key_name'],
key: 'api_key_name', key: 'api_key_name',
sorter: tableSorter(1), sorter: tableSorter(1),
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
@@ -29,7 +29,7 @@ const useModelsColumns = (): Array<{
}, },
{ {
title: 'User', title: 'User',
dataIndex: 'user_name', dataIndex: ['api_key', 'identity', 'value', 'user_name'],
key: 'user_name', key: 'user_name',
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
<AutoTooltip ghost style={{ maxWidth: 400 }}> <AutoTooltip ghost style={{ maxWidth: 400 }}>
+3 -3
View File
@@ -12,7 +12,7 @@ interface ColumnsHookProps {
const useModelsColumns = (): Array<{ const useModelsColumns = (): Array<{
title: string; title: string;
dataIndex: string; dataIndex: string | string[];
key: string; key: string;
}> => { }> => {
const intl = useIntl(); const intl = useIntl();
@@ -21,7 +21,7 @@ const useModelsColumns = (): Array<{
return [ return [
{ {
title: intl.formatMessage({ id: 'common.table.name' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'model_name', dataIndex: ['model', 'identity', 'value', 'model_name'],
key: 'model_name', key: 'model_name',
sorter: tableSorter(1), sorter: tableSorter(1),
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
@@ -34,7 +34,7 @@ const useModelsColumns = (): Array<{
}, },
{ {
title: 'Cluster', title: 'Cluster',
dataIndex: 'cluster_name', dataIndex: ['model', 'identity', 'value', 'cluster_name'],
key: 'cluster_name', key: 'cluster_name',
sorter: tableSorter(2), sorter: tableSorter(2),
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
+66 -7
View File
@@ -16,6 +16,7 @@ const DefaultDateConfig = {
type UserOptionType = UsageFilterItem & { type UserOptionType = UsageFilterItem & {
value: string; value: string;
}; };
type ValueType = string | number | null;
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>; type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
type GroupOptionType = GroupOption<UsageFilterItem>; type GroupOptionType = GroupOption<UsageFilterItem>;
@@ -29,21 +30,49 @@ interface UseUsageFiltersParams {
}; };
chartFilters: { chartFilters: {
metric: string; metric: string;
group_by: string; group_by: string | null;
granularity: string; granularity: string;
}; };
initialState?: {
activeModels?: ValueType[][];
activeApiKeys?: ValueType[][];
};
summaryColumns: { summaryColumns: {
title: string; title: string;
dataIndex: string; dataIndex: string;
key: string; key: string;
}[]; }[];
autoFetchOnFilterChange?: boolean;
onFetchData?: (params: {
chartFilters: UseUsageFiltersParams['chartFilters'];
filters: {
models?: FilterOptionType[];
users?: FilterOptionType[];
api_keys?: FilterOptionType[];
};
commonFilters: {
scope: string;
models: string[];
users: string[];
api_keys: string[];
start_date: string;
end_date: string;
};
}) => void;
} }
export const useUsageFilters = ({ export const useUsageFilters = ({
initialScope, initialScope,
metaData, metaData,
chartFilters, chartFilters,
summaryColumns summaryColumns,
initialState: {
activeModels: initialActiveModels = [],
activeApiKeys: initialActiveApiKeys = []
} = {},
autoFetchOnFilterChange = true,
onFetchData
}: UseUsageFiltersParams) => { }: UseUsageFiltersParams) => {
const { const {
detailData: timeSeriesData, detailData: timeSeriesData,
@@ -64,6 +93,10 @@ export const useUsageFilters = ({
const modelOptions = metaData?.models || []; const modelOptions = metaData?.models || [];
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] =
useState<ValueType[][]>(initialActiveApiKeys);
const buildFilters = (selected: typeof commonFilters) => { const buildFilters = (selected: typeof commonFilters) => {
const filters: { const filters: {
@@ -114,18 +147,31 @@ export const useUsageFilters = ({
currentSelectedFilters = commonFilters, currentSelectedFilters = commonFilters,
currentChartFilters = chartFilters currentChartFilters = chartFilters
) => { ) => {
const nextFilters = buildFilters(currentSelectedFilters);
if (onFetchData) {
onFetchData({
chartFilters: currentChartFilters,
filters: nextFilters,
commonFilters: currentSelectedFilters
});
return;
}
fetchTimeSeriesData({ fetchTimeSeriesData({
...currentChartFilters, ...currentChartFilters,
start_date: currentSelectedFilters.start_date, start_date: currentSelectedFilters.start_date,
end_date: currentSelectedFilters.end_date, end_date: currentSelectedFilters.end_date,
filters: buildFilters(currentSelectedFilters) filters: nextFilters
}); });
}; };
const handleScopeChange = (value: string) => { const handleScopeChange = (value: string) => {
const next = { ...commonFilters, scope: value }; const next = { ...commonFilters, scope: value };
setCommonFilters(next); setCommonFilters(next);
fetchData(next, chartFilters); if (autoFetchOnFilterChange) {
fetchData(next, chartFilters);
}
}; };
const handleDateChange = (_: any, dateStrings: [string, string]) => { const handleDateChange = (_: any, dateStrings: [string, string]) => {
@@ -133,16 +179,27 @@ export const useUsageFilters = ({
const [start_date, end_date] = dateStrings; const [start_date, end_date] = dateStrings;
const next = { ...commonFilters, start_date, end_date }; const next = { ...commonFilters, start_date, end_date };
setCommonFilters(next); setCommonFilters(next);
fetchData(next, chartFilters); if (autoFetchOnFilterChange) {
fetchData(next, chartFilters);
}
}; };
const handleFilterChange = ( const handleFilterChange = (
type: 'models' | 'users' | 'api_keys', type: 'models' | 'users' | 'api_keys',
value: string[] value: string[]
) => { ) => {
const next = { ...commonFilters, [type]: value }; 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;
});
const next = { ...commonFilters, [type]: selectedValues };
setCommonFilters(next); setCommonFilters(next);
fetchData(next, chartFilters); if (autoFetchOnFilterChange) {
fetchData(next, chartFilters);
}
}; };
const handleSearch = () => { const handleSearch = () => {
@@ -188,6 +245,8 @@ export const useUsageFilters = ({
modelOptions, modelOptions,
userOptions, userOptions,
apiKeyOptions, apiKeyOptions,
activeApiKeys,
activeModels,
handleSearch, handleSearch,
onScopeChange: handleScopeChange, onScopeChange: handleScopeChange,
onDateChange: handleDateChange, onDateChange: handleDateChange,
+2 -2
View File
@@ -11,7 +11,7 @@ interface ColumnsHookProps {
const useModelsColumns = (): Array<{ const useModelsColumns = (): Array<{
title: string; title: string;
dataIndex: string; dataIndex: string | string[];
key: string; key: string;
}> => { }> => {
const intl = useIntl(); const intl = useIntl();
@@ -20,7 +20,7 @@ const useModelsColumns = (): Array<{
return [ return [
{ {
title: intl.formatMessage({ id: 'common.table.name' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'user_name', dataIndex: ['user', 'identity', 'value', 'user_name'],
key: 'user_name', key: 'user_name',
sorter: tableSorter(1), sorter: tableSorter(1),
render: (text: string, record: ListItem) => ( render: (text: string, record: ListItem) => (
+37 -8
View File
@@ -11,11 +11,14 @@ import useExportTable from './hooks/use-export-table';
import { useUsageFilters } from './hooks/use-usage-filters'; import { useUsageFilters } from './hooks/use-usage-filters';
import useQueryUsageMetaData from './services/use-query-meta-data'; import useQueryUsageMetaData from './services/use-query-meta-data';
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
const Usage: React.FC = () => { const Usage: React.FC = () => {
const initialInfo = useModel('@@initialState'); const initialInfo = useModel('@@initialState');
const { initialState } = initialInfo || {}; const { initialState } = initialInfo || {};
const { exportTable } = useExportTable(); const { exportTable } = useExportTable();
const [openExportModal, setOpenExportModal] = useState(false); const [openExportModal, setOpenExportModal] = useState(false);
const [breakdownRefreshKey, setBreakdownRefreshKey] = useState(0);
const summaryColumns = [ const summaryColumns = [
{ {
@@ -47,11 +50,11 @@ const Usage: React.FC = () => {
const [chartFilters, setChartFilters] = useState<{ const [chartFilters, setChartFilters] = useState<{
metric: string; metric: string;
group_by: string; group_by: string | null;
granularity: string; granularity: string;
}>({ }>({
metric: 'total_tokens', metric: 'total_tokens',
group_by: 'model', group_by: null,
granularity: 'day' granularity: 'day'
}); });
@@ -118,14 +121,36 @@ const Usage: React.FC = () => {
]; ];
}, [timeSeriesData.summary]); }, [timeSeriesData.summary]);
const breakdownDateRange = useMemo(
() => ({
start_date: commonFilters.start_date,
end_date: commonFilters.end_date
}),
[commonFilters.end_date, commonFilters.start_date]
);
const handlePickerChange = (picker: DateType) => {
setChartFilters((prev) => ({
...prev,
granularity: picker === 'date' ? 'day' : picker
}));
};
const handleExportChart = () => { const handleExportChart = () => {
setOpenExportModal(true); setOpenExportModal(true);
}; };
const handleSearch = () => {
filterBar.handleSearch();
setBreakdownRefreshKey((prev) => prev + 1);
};
return ( return (
<div> <div>
<FilterBar <FilterBar
{...filterBar} {...filterBar}
handleSearch={handleSearch}
handlePickerChange={handlePickerChange}
onExportTable={exportTable} onExportTable={exportTable}
onExportChart={handleExportChart} onExportChart={handleExportChart}
/> />
@@ -139,9 +164,8 @@ const Usage: React.FC = () => {
height={80} height={80}
styles={{ styles={{
item: { item: {
borderBottom: '1px solid var(--ant-color-split)',
backgroundColor: 'var(--ant-color-fill-quaternary)', backgroundColor: 'var(--ant-color-fill-quaternary)',
borderRadius: '6px 6px 0 0' borderRadius: '6px'
} }
}} }}
/> />
@@ -159,17 +183,22 @@ const Usage: React.FC = () => {
/> />
<BreakdownTabs <BreakdownTabs
filters={filters} filters={filters}
dateRange={{ dateRange={breakdownDateRange}
start_date: commonFilters.start_date,
end_date: commonFilters.end_date
}}
scope={commonFilters.scope} scope={commonFilters.scope}
refreshKey={breakdownRefreshKey}
></BreakdownTabs> ></BreakdownTabs>
<ExportData <ExportData
metaData={metaData} metaData={metaData}
granularity={chartFilters.granularity}
initialScope={commonFilters.scope} initialScope={commonFilters.scope}
initialCommonFilters={commonFilters}
open={openExportModal} open={openExportModal}
handlePickerChange={handlePickerChange}
onCancel={() => setOpenExportModal(false)} onCancel={() => setOpenExportModal(false)}
initialState={{
activeModels: filterBar.activeModels,
activeApiKeys: filterBar.activeApiKeys
}}
></ExportData> ></ExportData>
</div> </div>
); );
+16 -5
View File
@@ -9,12 +9,14 @@ import { useEffect, useState } from 'react';
import { FilterOptionType } from '../config/types'; import { FilterOptionType } from '../config/types';
import useAPIKeys from '../hooks/use-apikeys-columns'; import useAPIKeys from '../hooks/use-apikeys-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';
const APIKeys: React.FC<{ const APIKeys: React.FC<{
apiKeys: FilterOptionType[]; apiKeys: FilterOptionType[];
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
}> = ({ apiKeys, dateRange, scope }) => { refreshKey?: number;
}> = ({ apiKeys, dateRange, scope, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -57,14 +59,23 @@ const APIKeys: React.FC<{
useEffect(() => { useEffect(() => {
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: 'api_key', group_by: ['api_key'],
filters: { filters: {
api_keys: apiKeys || [] api_keys: apiKeys
}, },
scope: scope, scope: scope,
...dateRange ...dateRange
}); });
}, [dateRange, apiKeys, queryParams, scope]); }, [
apiKeys,
dateRange.end_date,
dateRange.start_date,
queryParams.page,
queryParams.perPage,
queryParams.sort_by,
refreshKey,
scope
]);
return ( return (
<> <>
@@ -73,13 +84,13 @@ const APIKeys: React.FC<{
<Table <Table
columns={columns} columns={columns}
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
rowKey={getBreakdownRowKey}
loading={{ loading={{
spinning: loading, spinning: loading,
size: 'middle' size: 'middle'
}} }}
sortDirections={TABLE_SORT_DIRECTIONS} sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false} showSorterTooltip={false}
rowKey="id"
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{
size: 'middle', size: 'middle',
+20 -9
View File
@@ -9,12 +9,14 @@ import { useEffect, useState } from 'react';
import { FilterOptionType } from '../config/types'; import { FilterOptionType } from '../config/types';
import useUsersColumns from '../hooks/use-models-columns'; import useUsersColumns 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';
const Models: React.FC<{ const Models: React.FC<{
models: FilterOptionType[]; models: FilterOptionType[];
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
}> = ({ models, dateRange, scope }) => { refreshKey?: number;
}> = ({ models, dateRange, scope, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -43,13 +45,13 @@ const Models: React.FC<{
loading={loading} loading={loading}
loadend={dataSource.loadend} loadend={dataSource.loadend}
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
image={<IconFont type="icon-key" />} image={<IconFont type="icon-resources" />}
filters={_.omit(queryParams, ['sort_by'])} filters={_.omit(queryParams, ['sort_by'])}
noFoundText={intl.formatMessage({ noFoundText={intl.formatMessage({
id: 'noresult.keys.nofound' id: 'noresult.mymodels.nofound'
})} })}
title={intl.formatMessage({ id: 'noresult.keys.title' })} title={intl.formatMessage({ id: 'noresult.deployments.title' })}
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })} subTitle={intl.formatMessage({ id: 'noresult.deployments.subTitle' })}
></NoResult> ></NoResult>
); );
}; };
@@ -57,14 +59,23 @@ const Models: React.FC<{
useEffect(() => { useEffect(() => {
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: 'model', group_by: ['model'],
filters: { filters: {
models: models || [] models
}, },
scope: scope, scope: scope,
...dateRange ...dateRange
}); });
}, [dateRange, models, queryParams, scope]); }, [
dateRange.end_date,
dateRange.start_date,
models,
queryParams.page,
queryParams.perPage,
queryParams.sort_by,
refreshKey,
scope
]);
return ( return (
<> <>
@@ -73,13 +84,13 @@ const Models: React.FC<{
<Table <Table
columns={columns} columns={columns}
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
rowKey={getBreakdownRowKey}
loading={{ loading={{
spinning: loading, spinning: loading,
size: 'middle' size: 'middle'
}} }}
sortDirections={TABLE_SORT_DIRECTIONS} sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false} showSorterTooltip={false}
rowKey="id"
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{
size: 'middle', size: 'middle',
+20 -9
View File
@@ -9,12 +9,14 @@ import { useEffect, useState } from 'react';
import { FilterOptionType } from '../config/types'; import { FilterOptionType } from '../config/types';
import useUsersColumns from '../hooks/use-users-columns'; import useUsersColumns from '../hooks/use-users-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';
const Users: React.FC<{ const Users: React.FC<{
users: FilterOptionType[]; users: FilterOptionType[];
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
}> = ({ users, dateRange, scope }) => { refreshKey?: number;
}> = ({ users, dateRange, scope, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -43,13 +45,13 @@ const Users: React.FC<{
loading={loading} loading={loading}
loadend={dataSource.loadend} loadend={dataSource.loadend}
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
image={<IconFont type="icon-key" />} image={<IconFont type="icon-users" />}
filters={_.omit(queryParams, ['sort_by'])} filters={_.omit(queryParams, ['sort_by'])}
noFoundText={intl.formatMessage({ noFoundText={intl.formatMessage({
id: 'noresult.keys.nofound' id: 'noresult.users.nofound'
})} })}
title={intl.formatMessage({ id: 'noresult.keys.title' })} title={intl.formatMessage({ id: 'noresult.users.title' })}
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })} subTitle={intl.formatMessage({ id: 'noresult.users.subTitle' })}
></NoResult> ></NoResult>
); );
}; };
@@ -58,15 +60,24 @@ const Users: React.FC<{
if (scope === 'all') { if (scope === 'all') {
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: 'user', group_by: ['user'],
filters: { filters: {
users: users || [] users
}, },
scope: scope, scope: scope,
...dateRange ...dateRange
}); });
} }
}, [dateRange, users, queryParams, scope]); }, [
dateRange.end_date,
dateRange.start_date,
queryParams.page,
queryParams.perPage,
queryParams.sort_by,
refreshKey,
scope,
users
]);
return ( return (
<> <>
@@ -75,13 +86,13 @@ const Users: React.FC<{
<Table <Table
columns={columns} columns={columns}
dataSource={dataSource.dataList} dataSource={dataSource.dataList}
rowKey={getBreakdownRowKey}
loading={{ loading={{
spinning: loading, spinning: loading,
size: 'middle' size: 'middle'
}} }}
sortDirections={TABLE_SORT_DIRECTIONS} sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false} showSorterTooltip={false}
rowKey="id"
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{
size: 'middle', size: 'middle',
@@ -0,0 +1,35 @@
import { BreakdownItem } from '../config/types';
export const getBreakdownRowKey = (record: BreakdownItem): string => {
const identity = record?.identity;
const current = identity?.current;
const value = identity?.value;
const key = [
current?.model_id,
current?.user_id,
current?.api_key_id,
value?.model_name,
value?.user_name,
value?.api_key_name,
value?.cluster_name,
value?.provider_name,
value?.provider_type,
record?.label,
record?.cluster_name,
record?.model_name,
record?.user_name,
record?.api_key_name,
record?.last_active,
record?.input_tokens,
record?.output_tokens,
record?.total_tokens,
record?.api_requests
]
.filter((item) => item !== null && item !== undefined && item !== '')
.join('__');
return key || JSON.stringify(record);
};
export default getBreakdownRowKey;