import React, { PropsWithChildren, useCallback, useRef, useTransition } from 'react'; import { createContext, ContextSelector, useContextSelector } from '@fluentui/react-context-selector'; import { useMemoizedFn, useMount } from 'ahooks'; import { useBusterWebSocket } from '../BusterWebSocket'; import type { BusterMetricData } from '../Metrics'; import { MetricEvent_fetchingData } from '@/api/buster_socket/metrics/eventsInterfaces'; import { DEFAULT_MESSAGE_DATA } from './config'; import { createMockData } from './MOCK_DATA'; const useMetricData = () => { const busterSocket = useBusterWebSocket(); const [isPending, startTransition] = useTransition(); const metricDataRef = useRef>({}); const _setMetricData = useMemoizedFn( (metricId: string, newMetricData: Partial) => { metricDataRef.current = { ...metricDataRef.current, [metricId]: { ...DEFAULT_MESSAGE_DATA, ...metricDataRef.current[metricId], ...newMetricData, metricId } }; startTransition(() => { //trigger re-render }); } ); const _onGetFetchingData = useMemoizedFn((payload: MetricEvent_fetchingData) => { const { data, data_metadata, code, progress, metric_id: metricId } = payload; const currentMetric = getDataByMetricId(metricId); const fallbackData = data || currentMetric?.data; const fallbackDataMetadata = data_metadata || currentMetric?.data_metadata; const isCompleted = progress === 'completed'; onSetDataForMetric({ metricId, data: fallbackData, data_metadata: fallbackDataMetadata, isDataFromRerun: false, fetchedData: isCompleted, fetchingData: !isCompleted, code }); }); const onSetDataForMetric = useMemoizedFn( ({ metricId, data, data_metadata, isDataFromRerun, fetchedData, fetchingData, code }: { metricId: string; data: BusterMetricData['data']; data_metadata?: BusterMetricData['data_metadata']; isDataFromRerun?: boolean; fetchedData?: boolean; fetchingData?: boolean; code: string | null; }) => { const setKey = isDataFromRerun ? 'dataFromRerun' : 'data'; const prev = getDataByMetricId(metricId); _setMetricData(metricId, { [setKey]: data, fetchedAt: Date.now(), fetched: fetchedData ?? true, fetching: fetchingData ?? false, data_metadata: data_metadata ?? prev?.data_metadata, code }); } ); const onSetMetricDataCode = useMemoizedFn( ({ messageId, code }: { messageId: string; code: string }) => { _setMetricData(messageId, { code }); } ); const fetchDataByMetricId = useMemoizedFn(async ({ metricId }: { metricId: string }) => { const selectedMetricData = getDataByMetricId(metricId); if (selectedMetricData?.fetching || selectedMetricData?.fetched) { return; } _setMetricData(metricId, { fetching: true }); setTimeout(() => { //TODO: remove mock data // _setMetricData(metricId, { ...MOCK_DATA, fetched: true }); onSetDataForMetric(createMockData(metricId)); }, Math.random() * 5000); return await busterSocket.emitAndOnce({ emitEvent: { route: '/metrics/data', payload: { id: metricId } }, responseEvent: { route: '/metrics/get:fetchingData', callback: _onGetFetchingData } }); }); const getDataByMetricId = useCallback( (metricId: string | undefined) => { if (metricId && metricDataRef.current[metricId]) { return metricDataRef.current[metricId]; } return { ...DEFAULT_MESSAGE_DATA, metricId }; }, [isPending] ); const getDataByMetricIdMemoized = useMemoizedFn( (metricId: string): BusterMetricData | undefined => { return metricDataRef.current[metricId]; } ); useMount(() => { busterSocket.on({ route: '/metrics/get:fetchingData', callback: _onGetFetchingData }); }); return { metricData: metricDataRef.current, onSetMetricDataCode, onSetDataForMetric, fetchDataByMetricId, getDataByMetricId, getDataByMetricIdMemoized }; }; const BusterMetricDataContext = createContext>( {} as ReturnType ); export const BusterMetricDataProvider: React.FC = ({ children }) => { const messageDataContext = useMetricData(); return ( {children} ); }; BusterMetricDataProvider.displayName = 'BusterMetricDataProvider'; export const useBusterMetricDataContextSelector = ( selector: ContextSelector, T> ) => { return useContextSelector(BusterMetricDataContext, selector); };