mirror of https://github.com/buster-so/buster.git
metric individual subscriber
This commit is contained in:
parent
bb912b28ca
commit
5b4da9fc4a
|
@ -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<BusterMetric>({
|
||||
return useCreateReactQuery<IBusterMetric>({
|
||||
queryKey: ['metric', params],
|
||||
queryFn
|
||||
queryFn,
|
||||
enabled: false //this is handle via a socket query? maybe it should not be?
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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<TData, TError, TPayload>({
|
||||
mutationFn
|
||||
mutationFn,
|
||||
throwOnError: true
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<BusterMetric>({
|
||||
queryOptions<IBusterMetric>({
|
||||
queryKey: ['metrics', 'get', metricId] as const,
|
||||
staleTime: 10 * 1000,
|
||||
enabled: false
|
||||
|
|
|
@ -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<Record<string, IBusterMetric>>({});
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const getMetricId = useMemoizedFn((metricId?: string): string => {
|
||||
return metricId || selectedMetricId;
|
||||
});
|
||||
|
||||
const setMetrics = useMemoizedFn((newMetrics: Record<string, IBusterMetric>) => {
|
||||
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 = <T,>(
|
|||
) => {
|
||||
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
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
|
@ -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 };
|
||||
};
|
|
@ -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<Record<string, IBusterMetric>>;
|
||||
onInitializeMetric: (metric: BusterMetric) => void;
|
||||
setMetrics: (newMetrics: Record<string, IBusterMetric>) => 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
|
||||
};
|
||||
};
|
|
@ -8,12 +8,8 @@ import { useBusterNotifications } from '../../BusterNotifications';
|
|||
import { useBusterDashboardContextSelector } from '../../Dashboards';
|
||||
|
||||
export const useUpdateMetricAssosciations = ({
|
||||
metricsRef,
|
||||
setMetrics,
|
||||
getMetricMemoized
|
||||
}: {
|
||||
metricsRef: MutableRefObject<Record<string, IBusterMetric>>;
|
||||
setMetrics: (metrics: Record<string, IBusterMetric>) => void;
|
||||
getMetricMemoized: ({ metricId }: { metricId?: string }) => IBusterMetric;
|
||||
}) => {
|
||||
const busterSocket = useBusterWebSocket();
|
||||
|
|
|
@ -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<string, IBusterMetric>) => 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<IBusterMetric>, 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
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue