refactor: list watch trigger

This commit is contained in:
jialin
2026-02-13 16:12:56 +08:00
parent af9890b16c
commit 88aafbaf7b
6 changed files with 79 additions and 88 deletions
+42 -55
View File
@@ -1,4 +1,3 @@
import { WatchEventType } from '@/config';
import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useSetChunkRequest, { import useSetChunkRequest, {
createAxiosToken createAxiosToken
@@ -65,12 +64,6 @@ export default function useTableFetch<T>(
const axiosTokenRef = useRef<any>(null); const axiosTokenRef = useRef<any>(null);
const timerIDRef = useRef<any>(null); const timerIDRef = useRef<any>(null);
// ======= to resolve worker upate issue =======
const shouldUpdateRef = useRef(false);
const loadendRef = useRef(false);
const currentWatchParamsRef = useRef<any>(null);
// ============================================
// for skeleton loading // for skeleton loading
const [extraStatus, setExtraStatus] = useState<Record<string, any>>({ const [extraStatus, setExtraStatus] = useState<Record<string, any>>({
firstLoad: true firstLoad: true
@@ -97,23 +90,10 @@ export default function useTableFetch<T>(
...defaultQueryParams ...defaultQueryParams
}); });
// for recognize the current watch trigger time, so that we can ignore the previous events
const triggerAtRef = useRef<number>(0);
const { setChunkRequest } = useSetChunkRequest(); const { setChunkRequest } = useSetChunkRequest();
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
events: events,
dataList: dataSource.dataList,
setDataList(list, opts?: any) {
setDataSource((pre) => {
return {
total: pre.total,
totalPage: pre.totalPage,
loading: false,
loadend: true,
dataList: list,
deletedIds: opts?.deletedIds || []
};
});
}
});
const debounceSetExtraStatus = _.debounce(setExtraStatus, 3000); const debounceSetExtraStatus = _.debounce(setExtraStatus, 3000);
@@ -132,16 +112,12 @@ export default function useTableFetch<T>(
const params = { const params = {
..._.pickBy(query || queryParams, (val: any) => !!val) ..._.pickBy(query || queryParams, (val: any) => !!val)
}; };
currentWatchParamsRef.current = { chunkRequestRef.current?.current?.cancel?.();
query: { ...params }
};
axiosTokenRef.current?.cancel?.('CANCEL_PREVIOUS_REQUEST'); axiosTokenRef.current?.cancel?.('CANCEL_PREVIOUS_REQUEST');
axiosTokenRef.current = createAxiosToken(); axiosTokenRef.current = createAxiosToken();
const res = await fetchAPI(params, { const res = await fetchAPI(params, {
token: axiosTokenRef.current?.token token: axiosTokenRef.current?.token
}); });
shouldUpdateRef.current = false;
loadendRef.current = true;
if (!dataSource.loadend) { if (!dataSource.loadend) {
// add a delay to avoid flash // add a delay to avoid flash
await new Promise((resolve) => { await new Promise((resolve) => {
@@ -157,9 +133,6 @@ export default function useTableFetch<T>(
...params, ...params,
page: res.pagination.totalPage page: res.pagination.totalPage
}; };
currentWatchParamsRef.current = {
query: { ...newParams }
};
const newRes = await fetchAPI(newParams, { const newRes = await fetchAPI(newParams, {
token: axiosTokenRef.current?.token token: axiosTokenRef.current?.token
}); });
@@ -177,7 +150,7 @@ export default function useTableFetch<T>(
if (isInfiniteScroll) { if (isInfiniteScroll) {
setQueryParams(newParams); setQueryParams(newParams);
} }
return; return true;
} }
setDataSource({ setDataSource({
@@ -195,6 +168,7 @@ export default function useTableFetch<T>(
...query ...query
}); });
} }
return true;
} catch (error: any) { } catch (error: any) {
if (error.message !== 'CANCEL_PREVIOUS_REQUEST') { if (error.message !== 'CANCEL_PREVIOUS_REQUEST') {
setDataSource({ setDataSource({
@@ -205,9 +179,7 @@ export default function useTableFetch<T>(
totalPage: dataSource.totalPage totalPage: dataSource.totalPage
}); });
} }
return false;
loadendRef.current = true;
shouldUpdateRef.current = false;
} finally { } finally {
debounceSetExtraStatus({ debounceSetExtraStatus({
firstLoad: false firstLoad: false
@@ -216,32 +188,46 @@ export default function useTableFetch<T>(
}; };
// @ts-ignore // @ts-ignore
const debounceFetchData = _.debounce( const debounceFetchData = _.debounce(() => fetchData(), 300);
(params: any) => fetchData(params, true),
300 const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
); events: events,
dataList: dataSource.dataList,
triggerAt: updateManually ? triggerAtRef : undefined,
setDataList(list, opts?: any) {
setDataSource((pre) => {
return {
total: pre.total,
totalPage: pre.totalPage,
loading: false,
loadend: true,
dataList: list,
deletedIds: opts?.deletedIds || []
};
});
},
onCreate: (newItems: any) => {
// ======= to resolve worker update issue =======
if (updateManually && triggerAtRef.current && newItems.length > 0) {
debounceFetchData();
}
},
onDelete: (newItems: any) => {
// ======= to resolve worker update issue =======
if (updateManually && triggerAtRef.current && newItems.length > 0) {
debounceFetchData();
}
}
});
const updateHandler = (list: any) => { const updateHandler = (list: any) => {
_.each(list, (data: any) => { _.each(list, (data: any) => {
updateChunkedList(data); updateChunkedList(data);
}); });
shouldUpdateRef.current = list.some(
(data: any) =>
data.type === WatchEventType.DELETE ||
data.type === WatchEventType.CREATE
);
// ======= to resolve worker upate issue =======
// when worker list change (create/delete), fetch data again to update the list
if (shouldUpdateRef.current && updateManually && loadendRef.current) {
debounceFetchData(currentWatchParamsRef.current || undefined);
}
// ============================================
}; };
const createTableListChunkRequest = async (params?: any) => { const createTableListChunkRequest = async (params?: any) => {
if (!API || !watch) return; if (!API || !watch) return;
shouldUpdateRef.current = false;
chunkRequestRef.current?.current?.cancel?.(); chunkRequestRef.current?.current?.cancel?.();
try { try {
const currentParams = params || queryParams; const currentParams = params || queryParams;
@@ -252,6 +238,7 @@ export default function useTableFetch<T>(
url: `${API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`, url: `${API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`,
handler: updateHandler handler: updateHandler
}); });
triggerAtRef.current = Date.now();
} catch (error) { } catch (error) {
// ignore // ignore
} }
@@ -282,11 +269,10 @@ export default function useTableFetch<T>(
paginate?: boolean; paginate?: boolean;
} }
) => { ) => {
loadendRef.current = false;
const newQueryParams = { ...queryParams, ...params }; const newQueryParams = { ...queryParams, ...params };
setQueryParams(newQueryParams); setQueryParams(newQueryParams);
await fetchData({ query: newQueryParams }); const res = await fetchData({ query: newQueryParams });
if (watch && !options?.paginate) { if (watch && !options?.paginate && res) {
createTableListChunkRequest(newQueryParams); createTableListChunkRequest(newQueryParams);
} }
}; };
@@ -418,6 +404,7 @@ export default function useTableFetch<T>(
chunkRequestRef.current?.current?.cancel?.(); chunkRequestRef.current?.current?.cancel?.();
axiosTokenRef.current?.cancel?.(); axiosTokenRef.current?.cancel?.();
cacheDataListRef.current = []; cacheDataListRef.current = [];
triggerAtRef.current = 0;
}; };
}, []); }, []);
+12
View File
@@ -98,8 +98,16 @@ export function useUpdateChunkedList(options: {
// DELETE // DELETE
if (data?.type === WatchEventType.DELETE && events.includes('DELETE')) { if (data?.type === WatchEventType.DELETE && events.includes('DELETE')) {
const deletedList: any[] = [];
cacheDataListRef.current = cacheDataListRef.current?.filter( cacheDataListRef.current = cacheDataListRef.current?.filter(
(item: any) => { (item: any) => {
// collect deleted items
if (triggerAt?.current) {
if (ids?.includes(item.id)) {
deletedList.push(item);
}
}
return !ids?.includes(item.id); return !ids?.includes(item.id);
} }
); );
@@ -107,6 +115,10 @@ export function useUpdateChunkedList(options: {
options.setDataList?.([...cacheDataListRef.current], { options.setDataList?.([...cacheDataListRef.current], {
deletedIds: [...deletedIdsRef.current] deletedIds: [...deletedIdsRef.current]
}); });
console.log('deletedList:', deletedList);
options.onDelete?.(deletedList);
} }
// UPDATE // UPDATE
@@ -5,7 +5,6 @@ import { useIntl } from '@umijs/max';
import { Button, Input, Space } from 'antd'; import { Button, Input, Space } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
import { profileOptions } from '../../benchmark/config';
export interface RightActionsProps { export interface RightActionsProps {
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void; handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
@@ -82,23 +81,6 @@ const RightActions: React.FC<RightActionsProps> = ({
}) })
} }
></BaseSelect> ></BaseSelect>
<BaseSelect
allowClear
placeholder={intl.formatMessage({
id: 'benchmark.table.filter.byProfile'
})}
style={{ width: 200 }}
options={profileOptions.map((item) => ({
label: intl.formatMessage({ id: item.label }),
value: item.value
}))}
onChange={(value, option) =>
handleQueryChange({
profile: value,
page: 1
})
}
></BaseSelect>
<Button <Button
type="text" type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }} style={{ color: 'var(--ant-color-text-tertiary)' }}
@@ -10,7 +10,8 @@ import { ClusterListItem } from '../config/types';
* *
* @returns loading, fetch, dataList * @returns loading, fetch, dataList
*/ */
export const useQueryClusterList = () => { export const useQueryClusterList = (options?: { useStateData?: boolean }) => {
const { useStateData = true } = options || {};
const axiosTokenRef = useRef<CancelTokenSource | null>(null); const axiosTokenRef = useRef<CancelTokenSource | null>(null);
const [dataList, setDataList] = useState< const [dataList, setDataList] = useState<
Array<Partial<ClusterListItem> & { label: string; value: number }> Array<Partial<ClusterListItem> & { label: string; value: number }>
@@ -27,13 +28,15 @@ export const useQueryClusterList = () => {
const res = await queryClusterList(params, { const res = await queryClusterList(params, {
token: axiosTokenRef.current.token token: axiosTokenRef.current.token
}); });
setDataList( if (useStateData) {
res.items?.map((item: ClusterListItem) => ({ setDataList(
...item, res.items?.map((item: ClusterListItem) => ({
label: item.name, ...item,
value: item.id label: item.name,
})) || [] value: item.id
); })) || []
);
}
return res.items || []; return res.items || [];
}, },
{ {
+6 -3
View File
@@ -4,7 +4,7 @@ import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import NoResult from '@/pages/_components/no-result'; import NoResult from '@/pages/_components/no-result';
import PageBox from '@/pages/_components/page-box'; import PageBox from '@/pages/_components/page-box';
import { queryClusterList } from '@/pages/cluster-management/apis'; import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
import { useIntl, useSearchParams } from '@umijs/max'; import { useIntl, useSearchParams } from '@umijs/max';
import { ConfigProvider, Table } from 'antd'; import { ConfigProvider, Table } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
@@ -41,11 +41,14 @@ const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
const [clusterList, setClusterList] = useState<Global.BaseOption<number>[]>( const [clusterList, setClusterList] = useState<Global.BaseOption<number>[]>(
[] []
); );
const { fetchClusterList } = useQueryClusterList({
useStateData: false
});
const getClusterList = async () => { const getClusterList = async () => {
try { try {
const res = await queryClusterList({ page: -1 }); const items = await fetchClusterList({ page: -1 });
const list = res.items?.map((item) => ({ const list = items?.map((item) => ({
label: item.name, label: item.name,
value: item.id value: item.id
})); }));
+8 -4
View File
@@ -3,10 +3,10 @@ import { FilterBar } from '@/components/page-tools';
import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import PageBox from '@/pages/_components/page-box'; import PageBox from '@/pages/_components/page-box';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { DockerStepsFromWorker } from '@/pages/cluster-management/components/add-worker/config'; import { DockerStepsFromWorker } from '@/pages/cluster-management/components/add-worker/config';
import { ClusterListItem } from '@/pages/cluster-management/config/types'; import { ClusterListItem } from '@/pages/cluster-management/config/types';
import useAddWorker from '@/pages/cluster-management/hooks/use-add-worker'; import useAddWorker from '@/pages/cluster-management/hooks/use-add-worker';
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result'; import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result';
import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link'; import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -56,6 +56,7 @@ const Workers: React.FC<{
handleQueryChange, handleQueryChange,
handleNameChange handleNameChange
} = useTableFetch<ListItem>({ } = useTableFetch<ListItem>({
events: ['UPDATE', 'DELETE', 'CREATE'],
fetchAPI: queryWorkersList, fetchAPI: queryWorkersList,
deleteAPI: deleteWorker, deleteAPI: deleteWorker,
contentForDelete: 'resources.worker', contentForDelete: 'resources.worker',
@@ -71,6 +72,9 @@ const Workers: React.FC<{
}); });
const { MaintenanceModal, handleStopMaintenance, setOpenStatus } = const { MaintenanceModal, handleStopMaintenance, setOpenStatus } =
useWorkerMaintenance({ fetchData: handleSearch }); useWorkerMaintenance({ fetchData: handleSearch });
const { fetchClusterList } = useQueryClusterList({
useStateData: false
});
const intl = useIntl(); const intl = useIntl();
const [updateLabelsData, setUpdateLabelsData] = useState<{ const [updateLabelsData, setUpdateLabelsData] = useState<{
@@ -107,15 +111,15 @@ const Workers: React.FC<{
const params = { const params = {
page: -1 page: -1
}; };
const res = await queryClusterList(params); const items = await fetchClusterList(params);
const clusterMap = res?.items?.reduce( const clusterMap = items?.reduce(
(acc: Record<number, string>, item: any) => { (acc: Record<number, string>, item: any) => {
acc[item.id] = item.name; acc[item.id] = item.name;
return acc; return acc;
}, },
{} {}
); );
const list = res?.items?.map((item: any) => ({ const list = items?.map((item: any) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
id: item.id, id: item.id,