fix(usage): unique filter option values so same-named entries don't co-select
This commit is contained in:
@@ -137,12 +137,14 @@ export function groupToOptions<T, TChild>(
|
|||||||
options: {
|
options: {
|
||||||
getGroupKey: (item: T) => string;
|
getGroupKey: (item: T) => string;
|
||||||
getGroupType: (item: T) => string;
|
getGroupType: (item: T) => string;
|
||||||
getChild: (item: T) => TChild;
|
// ``index`` is a stable per-row token so ``getChild`` can mint a unique
|
||||||
|
// option value for id-less (deleted) entries that share a display name.
|
||||||
|
getChild: (item: T, index: number) => TChild;
|
||||||
}
|
}
|
||||||
): GroupOption<TChild>[] {
|
): GroupOption<TChild>[] {
|
||||||
const groupedMap = new Map<string, GroupOption<TChild>>();
|
const groupedMap = new Map<string, GroupOption<TChild>>();
|
||||||
|
|
||||||
data.forEach((item) => {
|
data.forEach((item, index) => {
|
||||||
const groupKey = options.getGroupKey(item);
|
const groupKey = options.getGroupKey(item);
|
||||||
const groupType = options.getGroupType(item);
|
const groupType = options.getGroupType(item);
|
||||||
|
|
||||||
@@ -156,7 +158,7 @@ export function groupToOptions<T, TChild>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const child = options.getChild(item) as TChild & { value: string };
|
const child = options.getChild(item, index) as TChild & { value: string };
|
||||||
groupedMap.get(groupKey)!.children.push(child);
|
groupedMap.get(groupKey)!.children.push(child);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export interface UsageFilterItem {
|
|||||||
identity: {
|
identity: {
|
||||||
value: {
|
value: {
|
||||||
cluster_name: string;
|
cluster_name: string;
|
||||||
model_name: string | null;
|
|
||||||
user_name: string;
|
user_name: string;
|
||||||
api_key_name: string | null;
|
api_key_name: string | null;
|
||||||
access_key: string | null;
|
access_key: string | null;
|
||||||
@@ -12,7 +11,6 @@ export interface UsageFilterItem {
|
|||||||
route_name: string | null;
|
route_name: string | null;
|
||||||
};
|
};
|
||||||
current: {
|
current: {
|
||||||
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;
|
route_id: number | null;
|
||||||
@@ -49,7 +47,6 @@ export interface TimeSeriesData {
|
|||||||
|
|
||||||
export type BreakdownItem = {
|
export type BreakdownItem = {
|
||||||
cluster_name: string;
|
cluster_name: string;
|
||||||
model_name: string;
|
|
||||||
user_name: string;
|
user_name: string;
|
||||||
api_key_name: string;
|
api_key_name: string;
|
||||||
input_tokens: number;
|
input_tokens: number;
|
||||||
@@ -88,7 +85,6 @@ export interface UsageBreakdownResponse {
|
|||||||
|
|
||||||
export interface UsageMeta {
|
export interface UsageMeta {
|
||||||
filters: {
|
filters: {
|
||||||
models: UsageFilterItem[];
|
|
||||||
users: UsageFilterItem[];
|
users: UsageFilterItem[];
|
||||||
api_keys: UsageFilterItem[];
|
api_keys: UsageFilterItem[];
|
||||||
routes: UsageFilterItem[];
|
routes: UsageFilterItem[];
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ type RouteOptionType = UsageFilterItem & {
|
|||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The multi-select keys options by ``value``; two entries sharing a display
|
||||||
|
// name (e.g. an active model and a deleted one with the same name) must get
|
||||||
|
// DIFFERENT values or selecting one selects the other. Prefer the real id;
|
||||||
|
// id-less (deleted) entries fall back to a per-row token so they stay
|
||||||
|
// independently selectable.
|
||||||
|
const optionValue = (
|
||||||
|
id: string | number | null | undefined,
|
||||||
|
index: number
|
||||||
|
): string => (id != null ? `id:${id}` : `row:${index}`);
|
||||||
|
|
||||||
export default function useQueryUsageMetaData() {
|
export default function useQueryUsageMetaData() {
|
||||||
const { detailData, loading, cancelRequest, fetchData } =
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
useQueryData<UsageMeta>({
|
useQueryData<UsageMeta>({
|
||||||
@@ -21,12 +31,10 @@ export default function useQueryUsageMetaData() {
|
|||||||
});
|
});
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
const [result, setResult] = useState<{
|
const [result, setResult] = useState<{
|
||||||
models: GroupOption<UsageFilterItem>[];
|
|
||||||
users: UserOptionType[];
|
users: UserOptionType[];
|
||||||
api_keys: GroupOption<UsageFilterItem>[];
|
api_keys: GroupOption<UsageFilterItem>[];
|
||||||
routes: RouteOptionType[];
|
routes: RouteOptionType[];
|
||||||
}>({
|
}>({
|
||||||
models: [],
|
|
||||||
users: [],
|
users: [],
|
||||||
api_keys: [],
|
api_keys: [],
|
||||||
routes: []
|
routes: []
|
||||||
@@ -50,19 +58,9 @@ export default function useQueryUsageMetaData() {
|
|||||||
const res = await fetchData({});
|
const res = await fetchData({});
|
||||||
const sortedUsers = sortUsers(res?.filters?.users || []);
|
const sortedUsers = sortUsers(res?.filters?.users || []);
|
||||||
const data = {
|
const data = {
|
||||||
models: groupToOptions(res?.filters?.models || [], {
|
|
||||||
getGroupKey: (item) => item.identity.value.provider_name || 'gpustack',
|
|
||||||
getGroupType: (item) =>
|
|
||||||
item.identity.value.provider_type || 'deployments',
|
|
||||||
getChild: (item) => ({
|
|
||||||
...item,
|
|
||||||
value: item.label,
|
|
||||||
label: item.identity.value.model_name || ''
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
users:
|
users:
|
||||||
sortedUsers.map((item) => ({
|
sortedUsers.map((item, index) => ({
|
||||||
value: item.label,
|
value: optionValue(item.identity.current?.user_id, index),
|
||||||
isCurrent:
|
isCurrent:
|
||||||
item.identity.current?.user_id === initialState?.currentUser?.id,
|
item.identity.current?.user_id === initialState?.currentUser?.id,
|
||||||
...item
|
...item
|
||||||
@@ -71,16 +69,16 @@ export default function useQueryUsageMetaData() {
|
|||||||
getGroupKey: (item) => item.identity.value.user_name || 'unknown_user',
|
getGroupKey: (item) => item.identity.value.user_name || 'unknown_user',
|
||||||
getGroupType: (item) =>
|
getGroupType: (item) =>
|
||||||
item.identity.value.api_key_is_custom ? 'custom' : 'default',
|
item.identity.value.api_key_is_custom ? 'custom' : 'default',
|
||||||
getChild: (item) => ({
|
getChild: (item, index) => ({
|
||||||
...item,
|
...item,
|
||||||
value: item.label,
|
value: optionValue(item.identity.current?.api_key_id, index),
|
||||||
label: item.identity.value.api_key_name || ''
|
label: item.identity.value.api_key_name || ''
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
routes:
|
routes:
|
||||||
(res?.filters?.routes || []).map((item) => ({
|
(res?.filters?.routes || []).map((item, index) => ({
|
||||||
...item,
|
...item,
|
||||||
value: item.label
|
value: optionValue(item.identity.current?.route_id, index)
|
||||||
})) || []
|
})) || []
|
||||||
};
|
};
|
||||||
setResult(data);
|
setResult(data);
|
||||||
|
|||||||
Reference in New Issue
Block a user