diff --git a/web/src/api/buster_rest/metrics/queryRequests.ts b/web/src/api/buster_rest/metrics/queryRequests.ts index c7a874d64..e69d9c010 100644 --- a/web/src/api/buster_rest/metrics/queryRequests.ts +++ b/web/src/api/buster_rest/metrics/queryRequests.ts @@ -4,15 +4,19 @@ import { useMemoizedFn } from 'ahooks'; import { getMetric, getMetric_server, listMetrics, listMetrics_server } from './requests'; import type { GetMetricParams, ListMetricsParams } from './interfaces'; import { BusterMetric, BusterMetricListItem } from '@/api/asset_interfaces'; +import { IBusterMetric } from '@/context/Metrics'; +import { upgradeMetricToIMetric } from '@/context/Metrics/helpers'; export const useGetMetric = (params: GetMetricParams) => { - const queryFn = useMemoizedFn(() => { - return getMetric(params); + const queryFn = useMemoizedFn(async () => { + const result = await getMetric(params); + return upgradeMetricToIMetric(result, null); }); - return useCreateReactQuery({ + return useCreateReactQuery({ queryKey: ['metric', params], - queryFn + queryFn, + enabled: false //this is handle via a socket query? maybe it should not be? }); }; diff --git a/web/src/api/buster_socket_query/useSocketQueryMutation.tsx b/web/src/api/buster_socket_query/useSocketQueryMutation.tsx index efd438edf..8fb85b70c 100644 --- a/web/src/api/buster_socket_query/useSocketQueryMutation.tsx +++ b/web/src/api/buster_socket_query/useSocketQueryMutation.tsx @@ -20,6 +20,7 @@ import type { InferBusterSocketResponseData } from './types'; import isEmpty from 'lodash/isEmpty'; +import { RustApiError } from '../buster_rest/errors'; /** * A custom hook that combines WebSocket communication with React Query's mutation capabilities. @@ -122,6 +123,9 @@ export function useSocketQueryMutation< } as BusterSocketRequest, responseEvent: { route: socketResponse, + onError: (error: RustApiError) => { + throw error; + }, callback: (d: unknown) => d } as BusterSocketResponse }); @@ -144,6 +148,7 @@ export function useSocketQueryMutation< ); return useMutation({ - mutationFn + mutationFn, + throwOnError: true }); } diff --git a/web/src/api/query_keys/metric.ts b/web/src/api/query_keys/metric.ts index 2c229aab3..1914dcda5 100644 --- a/web/src/api/query_keys/metric.ts +++ b/web/src/api/query_keys/metric.ts @@ -2,9 +2,10 @@ import { queryOptions } from '@tanstack/react-query'; import type { BusterMetric, BusterMetricListItem } from '@/api/asset_interfaces'; import type { MetricListRequest } from '@/api/request_interfaces/metrics'; import type { BusterMetricData } from '@/context/MetricData'; +import { IBusterMetric } from '@/context/Metrics'; export const metricsGetMetric = (metricId: string) => - queryOptions({ + queryOptions({ queryKey: ['metrics', 'get', metricId] as const, staleTime: 10 * 1000, enabled: false diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/BusterMetricsIndividualProvider.tsx b/web/src/context/Metrics/BusterMetricsIndividualProvider/BusterMetricsIndividualProvider.tsx index a2abb4529..d1245578d 100644 --- a/web/src/context/Metrics/BusterMetricsIndividualProvider/BusterMetricsIndividualProvider.tsx +++ b/web/src/context/Metrics/BusterMetricsIndividualProvider/BusterMetricsIndividualProvider.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren, useRef } from 'react'; +import React, { PropsWithChildren } from 'react'; import { createContext, ContextSelector, @@ -12,73 +12,44 @@ import { resolveEmptyMetric, upgradeMetricToIMetric } from '../helpers'; import { useUpdateMetricConfig } from './useMetricUpdateConfig'; import { useUpdateMetricAssosciations } from './useMetricUpdateAssosciations'; import { useShareMetric } from './useMetricShare'; -import { useMetricSubscribe } from './useMetricSubscribe'; import { useParams } from 'next/navigation'; -import { useMetricDataIndividual } from '@/context/MetricData/useMetricDataIndividual'; +import { useQueryClient } from '@tanstack/react-query'; +import { queryKeys } from '@/api/query_keys'; export const useBusterMetricsIndividual = () => { const [isPending, startTransition] = useTransition(); const { metricId: selectedMetricId } = useParams<{ metricId: string }>(); - const metricsRef = useRef>({}); + const queryClient = useQueryClient(); const getMetricId = useMemoizedFn((metricId?: string): string => { return metricId || selectedMetricId; }); - const setMetrics = useMemoizedFn((newMetrics: Record) => { - metricsRef.current = { ...metricsRef.current, ...newMetrics }; - startTransition(() => { - //trigger a rerender - }); - }); - - const resetMetric = useMemoizedFn(({ metricId }: { metricId: string }) => { - const prev = metricsRef.current; - delete prev[metricId]; - setMetrics(prev); - }); - //UI SELECTORS const getMetricMemoized = useMemoizedFn(({ metricId }: { metricId?: string }): IBusterMetric => { const _metricId = getMetricId(metricId); - const metrics = metricsRef.current || {}; - const currentMetric = metrics[_metricId]; - return resolveEmptyMetric(currentMetric, _metricId); + const options = queryKeys['/metrics/get:getMetric'](_metricId); + const data = queryClient.getQueryData(options.queryKey); + return resolveEmptyMetric(data, _metricId); }); //STATE UPDATERS const onInitializeMetric = useMemoizedFn((newMetric: BusterMetric) => { - const metrics = metricsRef.current || {}; - - const oldMetric = metrics[newMetric.id] as IBusterMetric | undefined; //HMMM is this right? - + const oldMetric = getMetricMemoized({ metricId: newMetric.id }); const upgradedMetric = upgradeMetricToIMetric(newMetric, oldMetric); - metricUpdateConfig.onUpdateMetric(upgradedMetric, false); }); // EMITTERS - const metricSubscribe = useMetricSubscribe({ - metricsRef, - setMetrics, - onInitializeMetric - }); - const metricShare = useShareMetric({ onInitializeMetric }); - const metricAssosciations = useUpdateMetricAssosciations({ - metricsRef, - setMetrics, - getMetricMemoized - }); + const metricAssosciations = useUpdateMetricAssosciations({ getMetricMemoized }); const metricUpdateConfig = useUpdateMetricConfig({ getMetricId, - setMetrics, - startTransition, onInitializeMetric, getMetricMemoized }); @@ -87,11 +58,8 @@ export const useBusterMetricsIndividual = () => { ...metricAssosciations, ...metricShare, ...metricUpdateConfig, - ...metricSubscribe, - resetMetric, onInitializeMetric, - getMetricMemoized, - metrics: metricsRef.current + getMetricMemoized }; }; @@ -115,21 +83,3 @@ export const useBusterMetricsIndividualContextSelector = ( ) => { return useContextSelector(BusterMetricsIndividual, selector); }; - -export const useBusterMetricIndividual = ({ metricId }: { metricId: string }) => { - const subscribeToMetric = useBusterMetricsIndividualContextSelector((x) => x.subscribeToMetric); - const metric = useBusterMetricsIndividualContextSelector((x) => x.metrics[metricId]); - - const metricIndividualData = useMetricDataIndividual({ - metricId - }); - - useMount(() => { - subscribeToMetric({ metricId }); - }); - - return { - metric: resolveEmptyMetric(metric, metricId), - ...metricIndividualData - }; -}; diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/useBusterMetricIndividual.ts b/web/src/context/Metrics/BusterMetricsIndividualProvider/useBusterMetricIndividual.ts new file mode 100644 index 000000000..17dd15632 --- /dev/null +++ b/web/src/context/Metrics/BusterMetricsIndividualProvider/useBusterMetricIndividual.ts @@ -0,0 +1,51 @@ +import { useMetricDataIndividual } from '@/context/MetricData'; +import { useBusterMetricsIndividualContextSelector } from './BusterMetricsIndividualProvider'; +import { useSocketQueryEmitOn } from '@/api/buster_socket_query'; +import { queryKeys } from '@/api/query_keys'; +import { resolveEmptyMetric, upgradeMetricToIMetric } from '../helpers'; +import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider'; +import { useEffect } from 'react'; + +export const useBusterMetricIndividual = ({ metricId }: { metricId: string }) => { + const onInitializeMetric = useBusterMetricsIndividualContextSelector((x) => x.onInitializeMetric); + const getMetricMemoized = useBusterMetricsIndividualContextSelector((x) => x.getMetricMemoized); + const getAssetPassword = useBusterAssetsContextSelector((state) => state.getAssetPassword); + const setAssetPasswordError = useBusterAssetsContextSelector( + (state) => state.setAssetPasswordError + ); + + const assetPassword = getAssetPassword(metricId); + + const { + data: metric, + refetch: refetchMetric, + isFetched: isMetricFetched, + error: metricError + } = useSocketQueryEmitOn( + { route: '/metrics/get', payload: { id: metricId, password: assetPassword.password } }, + '/metrics/get:updateMetricState', + queryKeys['/metrics/get:getMetric'](metricId), + (currentData, newData) => { + return upgradeMetricToIMetric(newData, currentData); + } + ); + + const metricIndividualData = useMetricDataIndividual({ + metricId + }); + + useEffect(() => { + if (metricError) { + setAssetPasswordError(metricId, metricError.message || 'An error occurred'); + } else { + setAssetPasswordError(metricId, null); + } + }, [metricError]); + + return { + metric: resolveEmptyMetric(metric, metricId), + isMetricFetched, + refetchMetric, + ...metricIndividualData + }; +}; diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricFetched.ts b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricFetched.ts deleted file mode 100644 index d2b697905..000000000 --- a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricFetched.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useBusterMetricsIndividualContextSelector } from './BusterMetricsIndividualProvider'; - -export const useMetricFetched = ({ metricId }: { metricId: string }) => { - const fetched = useBusterMetricsIndividualContextSelector((x) => x.metrics[metricId]?.fetched); - const fetching = useBusterMetricsIndividualContextSelector((x) => x.metrics[metricId]?.fetching); - const error = useBusterMetricsIndividualContextSelector((x) => x.metrics[metricId]?.error); - - return { fetched, fetching, error }; -}; diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricSubscribe.ts b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricSubscribe.ts deleted file mode 100644 index cc1d23960..000000000 --- a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricSubscribe.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useMemoizedFn } from 'ahooks'; -import { useBusterAssetsContextSelector } from '../../Assets/BusterAssetsProvider'; -import { IBusterMetric } from '../interfaces'; -import { useBusterWebSocket } from '../../BusterWebSocket'; -import { BusterMetric } from '@/api/asset_interfaces'; -import { RustApiError } from '@/api/buster_rest/errors'; -import { resolveEmptyMetric } from '../helpers'; -import React from 'react'; - -export const useMetricSubscribe = ({ - metricsRef, - onInitializeMetric, - setMetrics -}: { - metricsRef: React.MutableRefObject>; - onInitializeMetric: (metric: BusterMetric) => void; - setMetrics: (newMetrics: Record) => void; -}) => { - const busterSocket = useBusterWebSocket(); - const getAssetPassword = useBusterAssetsContextSelector((state) => state.getAssetPassword); - const setAssetPasswordError = useBusterAssetsContextSelector( - (state) => state.setAssetPasswordError - ); - - const _onGetMetricState = useMemoizedFn((metric: BusterMetric) => { - onInitializeMetric(metric); - }); - - const _onGetMetricStateError = useMemoizedFn((_error: any, metricId: string) => { - const error = _error as RustApiError; - setAssetPasswordError(metricId, error.message || 'An error occurred'); - }); - - const _setLoadingMetric = useMemoizedFn((metricId: string) => { - const metrics = metricsRef.current || {}; - metrics[metricId] = resolveEmptyMetric( - { - ...metrics[metricId], - fetching: true - }, - metricId - ); - - setMetrics(metrics); - - return metrics[metricId]; - }); - - const subscribeToMetric = useMemoizedFn(async ({ metricId }: { metricId: string }) => { - const { password } = getAssetPassword(metricId); - const foundMetric: undefined | IBusterMetric = metricsRef.current[metricId]; - - if (foundMetric && (foundMetric?.fetching || foundMetric?.fetched)) { - return foundMetric; - } - - _setLoadingMetric(metricId); - - return await busterSocket.emitAndOnce({ - emitEvent: { - route: '/metrics/get', - payload: { - id: metricId, - password - } - }, - responseEvent: { - route: '/metrics/get:updateMetricState', - callback: _onGetMetricState, - onError: (error) => _onGetMetricStateError(error, metricId) - } - }); - }); - - const unsubscribeToMetricEvents = useMemoizedFn(({ metricId }: { metricId: string }) => { - busterSocket.off({ - route: '/metrics/get:updateMetricState', - callback: onInitializeMetric - }); - busterSocket.off({ - route: '/metrics/update:updateMetricState', - callback: onInitializeMetric - }); - }); - - return { - unsubscribeToMetricEvents, - subscribeToMetric - }; -}; diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts index 33cd78f6e..10f715166 100644 --- a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts +++ b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts @@ -8,12 +8,8 @@ import { useBusterNotifications } from '../../BusterNotifications'; import { useBusterDashboardContextSelector } from '../../Dashboards'; export const useUpdateMetricAssosciations = ({ - metricsRef, - setMetrics, getMetricMemoized }: { - metricsRef: MutableRefObject>; - setMetrics: (metrics: Record) => void; getMetricMemoized: ({ metricId }: { metricId?: string }) => IBusterMetric; }) => { const busterSocket = useBusterWebSocket(); diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateConfig.ts b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateConfig.ts index 506912cfc..ec92f149c 100644 --- a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateConfig.ts +++ b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateConfig.ts @@ -8,24 +8,29 @@ import { VerificationStatus } from '@/api/asset_interfaces'; import { prepareMetricUpdateMetric } from '../helpers'; -import { MetricUpdateMetric } from '@/api/buster_socket/metrics'; import { ColumnSettings, IColumnLabelFormat } from '@/components/charts'; -import { useBusterWebSocket } from '../../BusterWebSocket'; +import { useTransition } from 'react'; +import { queryKeys } from '@/api/query_keys'; +import { useQueryClient } from '@tanstack/react-query'; +import { useSocketQueryMutation } from '@/api/buster_socket_query'; export const useUpdateMetricConfig = ({ getMetricId, getMetricMemoized, - setMetrics, - startTransition, onInitializeMetric }: { getMetricMemoized: ({ metricId }: { metricId?: string }) => IBusterMetric; onInitializeMetric: (metric: BusterMetric) => void; getMetricId: (metricId?: string) => string; - setMetrics: (metrics: Record) => void; - startTransition: (fn: () => void) => void; }) => { - const busterSocket = useBusterWebSocket(); + const [isPending, startTransition] = useTransition(); + const queryClient = useQueryClient(); + + const setMetricToState = useMemoizedFn((metric: IBusterMetric) => { + const metricId = getMetricId(metric.id); + const options = queryKeys['/metrics/get:getMetric'](metricId); + queryClient.setQueryData(options.queryKey, metric); + }); const onUpdateMetric = useMemoizedFn( async (newMetricPartial: Partial, saveToServer: boolean = true) => { @@ -35,10 +40,7 @@ export const useUpdateMetricConfig = ({ ...currentMetric, ...newMetricPartial }; - setMetrics({ - [metricId]: newMetric - }); - + setMetricToState(newMetric); //This will trigger a rerender and push prepareMetricUpdateMetric off UI metric startTransition(() => { const isReadyOnly = currentMetric.permission === ShareRole.VIEWER; @@ -46,48 +48,39 @@ export const useUpdateMetricConfig = ({ _prepareMetricAndSaveToServer(newMetric, currentMetric); } }); - return newMetric; } ); - const _CheckUpdateMetric = useMemoizedFn((metric: BusterMetric) => { - const draftSessionId = metric.draft_session_id; - const currentMessage = getMetricMemoized({ metricId: metric.id }); - if (draftSessionId && !currentMessage?.draft_session_id) { - onUpdateMetric({ - id: metric.id, - draft_session_id: draftSessionId - }); - } - return metric; - }); - - const updateMetricToServer = useMemoizedFn((payload: MetricUpdateMetric['payload']) => { - return busterSocket.emitAndOnce({ - emitEvent: { - route: '/metrics/update', - payload - }, - responseEvent: { - route: '/metrics/update:updateMetricState', - callback: _CheckUpdateMetric + const { mutateAsync: updateMetricToServer } = useSocketQueryMutation( + '/metrics/update', + '/metrics/update:updateMetricState', + null, + null, + (metric, currentData, variables) => { + const draftSessionId = metric.draft_session_id; + const currentMessage = getMetricMemoized({ metricId: metric.id }); + if (draftSessionId && !currentMessage?.draft_session_id) { + onUpdateMetric( + { + id: metric.id, + draft_session_id: draftSessionId + }, + false + ); } - }); - }); - - const { run: _updateMetricToServer } = useDebounceFn(updateMetricToServer, { - wait: 300 - }); + return metric; + } + ); const { run: _prepareMetricAndSaveToServer } = useDebounceFn( useMemoizedFn((newMetric: IBusterMetric, oldMetric: IBusterMetric) => { const changedValues = prepareMetricUpdateMetric(newMetric, oldMetric); if (changedValues) { - _updateMetricToServer(changedValues); + updateMetricToServer(changedValues); } }), - { wait: 700 } + { wait: 750 } ); const onUpdateMetricChartConfig = useMemoizedFn( @@ -185,26 +178,10 @@ export const useUpdateMetricConfig = ({ ); const onSaveMetricChanges = useMemoizedFn( - async ({ - metricId, - ...params - }: { - metricId: string; - save_draft: boolean; - save_as_metric_state?: string; - }) => { - return busterSocket.emitAndOnce({ - emitEvent: { - route: '/metrics/update', - payload: { - id: metricId, - ...params - } - }, - responseEvent: { - route: '/metrics/update:updateMetricState', - callback: onInitializeMetric - } + async (params: { metricId: string; save_draft: boolean; save_as_metric_state?: string }) => { + return updateMetricToServer({ + id: params.metricId, + ...params }); } );