mirror of https://github.com/buster-so/buster.git
create a hook for update charts
This commit is contained in:
parent
77ece34d25
commit
ae38da2de1
|
@ -1,6 +1,6 @@
|
|||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useMemoizedFn, useDebounceFn } from '@/hooks';
|
||||
import {
|
||||
deleteMetrics,
|
||||
getMetric,
|
||||
|
@ -13,7 +13,7 @@ import {
|
|||
import type { GetMetricParams, ListMetricsParams, UpdateMetricParams } from './interfaces';
|
||||
import { upgradeMetricToIMetric } from '@/lib/chat';
|
||||
import { queryKeys } from '@/api/query_keys';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useTransition } from 'react';
|
||||
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
|
||||
import { resolveEmptyMetric } from '@/lib/metrics/resolve';
|
||||
import { useGetUserFavorites } from '../users';
|
||||
|
@ -150,10 +150,13 @@ export const useSaveMetric = () => {
|
|||
* It will also strip out any values that are not changed from the DEFAULT_CHART_CONFIG.
|
||||
* It will also update the draft_session_id if it exists.
|
||||
*/
|
||||
export const useUpdateMetric = () => {
|
||||
export const useUpdateMetric = (params?: { wait?: number }) => {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: saveMetric } = useSaveMetric();
|
||||
const mutationFn = useMemoizedFn(
|
||||
const waitTime = params?.wait || 0;
|
||||
|
||||
const combineAndSaveMetric = useMemoizedFn(
|
||||
async (newMetricPartial: Partial<IBusterMetric> & { id: string }) => {
|
||||
const metricId = newMetricPartial.id;
|
||||
const options = queryKeys.metricsGetMetric(metricId);
|
||||
|
@ -163,17 +166,41 @@ export const useUpdateMetric = () => {
|
|||
});
|
||||
|
||||
if (prevMetric && newMetric) {
|
||||
queryClient.setQueryData(options.queryKey, newMetric);
|
||||
}
|
||||
|
||||
return { newMetric, prevMetric };
|
||||
}
|
||||
);
|
||||
|
||||
const mutationFn = useMemoizedFn(
|
||||
async (newMetricPartial: Partial<IBusterMetric> & { id: string }) => {
|
||||
const { newMetric, prevMetric } = await combineAndSaveMetric(newMetricPartial);
|
||||
if (newMetric && prevMetric) {
|
||||
startTransition(() => {
|
||||
const changedValues = prepareMetricUpdateMetric(newMetric, prevMetric);
|
||||
if (changedValues) {
|
||||
return saveMetric(changedValues);
|
||||
saveMetric(changedValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(newMetric!);
|
||||
}
|
||||
);
|
||||
return useMutation({
|
||||
mutationFn
|
||||
|
||||
const mutationRes = useMutation({
|
||||
mutationFn: mutationFn
|
||||
});
|
||||
|
||||
const { run: mutateDebounced } = useDebounceFn(mutationRes.mutateAsync, { wait: waitTime });
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...mutationRes,
|
||||
mutateDebounced
|
||||
}),
|
||||
[mutationRes, mutateDebounced]
|
||||
);
|
||||
};
|
||||
|
||||
export const useDeleteMetric = () => {
|
||||
|
|
|
@ -16,12 +16,10 @@ import { useDebounceFn, useMemoizedFn } from '@/hooks';
|
|||
import { prepareMetricUpdateMetric, resolveEmptyMetric } from '@/lib/metrics';
|
||||
import { create } from 'mutative';
|
||||
import { ShareRole } from '@/api/asset_interfaces/share';
|
||||
import { useUpdateMetric } from '@/api/buster_rest/metrics';
|
||||
|
||||
const useBusterMetrics = () => {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const { metricId: selectedMetricId } = useParams<{ metricId: string }>();
|
||||
const { mutateAsync: updateMetricMutation } = useUpdateMetric();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const getMetricId = useMemoizedFn((metricId?: string): string => {
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import {
|
||||
ColumnSettings,
|
||||
DEFAULT_CHART_CONFIG,
|
||||
IColumnLabelFormat,
|
||||
type IBusterMetric,
|
||||
type IBusterMetricChartConfig
|
||||
} from '@/api/asset_interfaces/metric';
|
||||
import { useUpdateMetric } from '@/api/buster_rest/metrics';
|
||||
import { queryKeys } from '@/api/query_keys';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { resolveEmptyMetric } from '@/lib/metrics/resolve';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const useUpdateMetricChart = ({ metricId }: { metricId: string }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateDebounced: onUpdateMetricDebounced } = useUpdateMetric({ wait: 600 });
|
||||
|
||||
const getMetricMemoized = useMemoizedFn((metricIdProp?: string): IBusterMetric => {
|
||||
const options = queryKeys.metricsGetMetric(metricIdProp || metricId);
|
||||
const data = queryClient.getQueryData(options.queryKey);
|
||||
return resolveEmptyMetric(data, metricIdProp || metricId);
|
||||
});
|
||||
|
||||
const onUpdateMetricChartConfig = useMemoizedFn(
|
||||
({
|
||||
chartConfig,
|
||||
ignoreUndoRedo
|
||||
}: {
|
||||
chartConfig: Partial<IBusterMetricChartConfig>;
|
||||
ignoreUndoRedo?: boolean;
|
||||
}) => {
|
||||
const currentMetric = getMetricMemoized();
|
||||
|
||||
if (!ignoreUndoRedo) {
|
||||
// undoRedoParams.addToUndoStack({
|
||||
// metricId: editMetric.id,
|
||||
// messageId: editMessage.id,
|
||||
// chartConfig: editMessage.chart_config
|
||||
// });
|
||||
}
|
||||
|
||||
const newChartConfig: IBusterMetricChartConfig = {
|
||||
...DEFAULT_CHART_CONFIG,
|
||||
...currentMetric.chart_config,
|
||||
...chartConfig
|
||||
};
|
||||
onUpdateMetricDebounced({
|
||||
id: metricId,
|
||||
chart_config: newChartConfig
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const onUpdateColumnLabelFormat = useMemoizedFn(
|
||||
({
|
||||
columnId,
|
||||
columnLabelFormat
|
||||
}: {
|
||||
columnId: string;
|
||||
columnLabelFormat: Partial<IColumnLabelFormat>;
|
||||
}) => {
|
||||
const currentMetric = getMetricMemoized();
|
||||
const existingColumnLabelFormats = currentMetric.chart_config.columnLabelFormats;
|
||||
const existingColumnLabelFormat = existingColumnLabelFormats[columnId];
|
||||
const newColumnLabelFormat = {
|
||||
...existingColumnLabelFormat,
|
||||
...columnLabelFormat
|
||||
};
|
||||
const columnLabelFormats = {
|
||||
...existingColumnLabelFormats,
|
||||
[columnId]: newColumnLabelFormat
|
||||
};
|
||||
onUpdateMetricChartConfig({
|
||||
chartConfig: {
|
||||
columnLabelFormats
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const onUpdateColumnSetting = useMemoizedFn(
|
||||
({ columnId, columnSetting }: { columnId: string; columnSetting: Partial<ColumnSettings> }) => {
|
||||
const currentMetric = getMetricMemoized();
|
||||
const existingColumnSettings = currentMetric.chart_config.columnSettings;
|
||||
const existingColumnSetting = currentMetric.chart_config.columnSettings[columnId];
|
||||
const newColumnSetting: Required<ColumnSettings> = {
|
||||
...existingColumnSetting,
|
||||
...columnSetting
|
||||
};
|
||||
const newColumnSettings: Record<string, Required<ColumnSettings>> = {
|
||||
...existingColumnSettings,
|
||||
[columnId]: newColumnSetting
|
||||
};
|
||||
onUpdateMetricChartConfig({
|
||||
chartConfig: {
|
||||
columnSettings: newColumnSettings
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
onUpdateMetricChartConfig,
|
||||
onUpdateColumnLabelFormat,
|
||||
onUpdateColumnSetting
|
||||
};
|
||||
};
|
|
@ -1,6 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import useLatest from './useLatest';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useUnmount } from './useUnmount';
|
||||
|
||||
interface DebounceOptions {
|
||||
wait?: number;
|
||||
|
@ -8,79 +11,38 @@ interface DebounceOptions {
|
|||
leading?: boolean;
|
||||
}
|
||||
|
||||
export function useDebounceFn<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
options: DebounceOptions = {}
|
||||
) {
|
||||
const { wait = 1000, maxWait, leading = false } = options;
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const maxTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const fnRef = useRef<T>(fn);
|
||||
const argsRef = useRef<Parameters<T> | null>(null);
|
||||
const lastCallTime = useRef<number>(0);
|
||||
type noop = (...args: any[]) => any;
|
||||
|
||||
// Update the function ref when fn changes
|
||||
useEffect(() => {
|
||||
fnRef.current = fn;
|
||||
}, [fn]);
|
||||
export function useDebounceFn<T extends noop>(fn: T, options?: DebounceOptions) {
|
||||
const fnRef = useLatest(fn);
|
||||
|
||||
const cancel = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
if (maxTimeoutRef.current) {
|
||||
clearTimeout(maxTimeoutRef.current);
|
||||
}
|
||||
}, []);
|
||||
const wait = options?.wait ?? 1000;
|
||||
|
||||
const run = useCallback(
|
||||
(...args: Parameters<T>) => {
|
||||
argsRef.current = args;
|
||||
const now = Date.now();
|
||||
|
||||
if (leading && !timeoutRef.current) {
|
||||
fnRef.current(...args);
|
||||
lastCallTime.current = now;
|
||||
}
|
||||
|
||||
cancel();
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
if (!leading && argsRef.current) {
|
||||
fnRef.current(...argsRef.current);
|
||||
}
|
||||
lastCallTime.current = now;
|
||||
}, wait);
|
||||
|
||||
// Handle maxWait
|
||||
if (maxWait && !maxTimeoutRef.current && !leading) {
|
||||
const timeSinceLastCall = now - lastCallTime.current;
|
||||
const maxWaitTimeRemaining = Math.max(0, maxWait - timeSinceLastCall);
|
||||
|
||||
maxTimeoutRef.current = setTimeout(() => {
|
||||
if (timeoutRef.current && argsRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
fnRef.current(...argsRef.current);
|
||||
lastCallTime.current = Date.now();
|
||||
}
|
||||
maxTimeoutRef.current = undefined;
|
||||
}, maxWaitTimeRemaining);
|
||||
}
|
||||
const debounced = useMemo(
|
||||
() =>
|
||||
debounce(
|
||||
(...args: Parameters<T>): ReturnType<T> => {
|
||||
return fnRef.current(...args);
|
||||
},
|
||||
[wait, maxWait, leading, cancel]
|
||||
wait,
|
||||
options
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// Clean up timeouts on unmount
|
||||
useEffect(() => {
|
||||
return () => cancel();
|
||||
}, [cancel]);
|
||||
useUnmount(() => {
|
||||
debounced.cancel();
|
||||
});
|
||||
|
||||
return {
|
||||
run,
|
||||
cancel
|
||||
run: debounced,
|
||||
cancel: debounced.cancel,
|
||||
flush: debounced.flush
|
||||
};
|
||||
}
|
||||
|
||||
export default useDebounceFn;
|
||||
|
||||
export function useDebounce<T>(value: T, options: DebounceOptions = {}) {
|
||||
const { wait = 1000, maxWait } = options;
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
|
||||
export function useLatest<T>(value: T) {
|
||||
const ref = useRef(value);
|
||||
ref.current = value;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
export default useLatest;
|
Loading…
Reference in New Issue