buster/web/src/context/MetricData/BusterMetricDataProvider.tsx

174 lines
4.9 KiB
TypeScript

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<Record<string, BusterMetricData>>({});
const _setMetricData = useMemoizedFn(
(metricId: string, newMetricData: Partial<BusterMetricData>) => {
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<ReturnType<typeof useMetricData>>(
{} as ReturnType<typeof useMetricData>
);
export const BusterMetricDataProvider: React.FC<PropsWithChildren> = ({ children }) => {
const messageDataContext = useMetricData();
return (
<BusterMetricDataContext.Provider value={messageDataContext}>
{children}
</BusterMetricDataContext.Provider>
);
};
BusterMetricDataProvider.displayName = 'BusterMetricDataProvider';
export const useBusterMetricDataContextSelector = <T,>(
selector: ContextSelector<ReturnType<typeof useMetricData>, T>
) => {
return useContextSelector(BusterMetricDataContext, selector);
};