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 { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn, useDebounceFn } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
deleteMetrics,
|
deleteMetrics,
|
||||||
getMetric,
|
getMetric,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
||||||
import type { GetMetricParams, ListMetricsParams, UpdateMetricParams } from './interfaces';
|
import type { GetMetricParams, ListMetricsParams, UpdateMetricParams } from './interfaces';
|
||||||
import { upgradeMetricToIMetric } from '@/lib/chat';
|
import { upgradeMetricToIMetric } from '@/lib/chat';
|
||||||
import { queryKeys } from '@/api/query_keys';
|
import { queryKeys } from '@/api/query_keys';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useTransition } from 'react';
|
||||||
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
|
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
|
||||||
import { resolveEmptyMetric } from '@/lib/metrics/resolve';
|
import { resolveEmptyMetric } from '@/lib/metrics/resolve';
|
||||||
import { useGetUserFavorites } from '../users';
|
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 strip out any values that are not changed from the DEFAULT_CHART_CONFIG.
|
||||||
* It will also update the draft_session_id if it exists.
|
* 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 queryClient = useQueryClient();
|
||||||
const { mutateAsync: saveMetric } = useSaveMetric();
|
const { mutateAsync: saveMetric } = useSaveMetric();
|
||||||
const mutationFn = useMemoizedFn(
|
const waitTime = params?.wait || 0;
|
||||||
|
|
||||||
|
const combineAndSaveMetric = useMemoizedFn(
|
||||||
async (newMetricPartial: Partial<IBusterMetric> & { id: string }) => {
|
async (newMetricPartial: Partial<IBusterMetric> & { id: string }) => {
|
||||||
const metricId = newMetricPartial.id;
|
const metricId = newMetricPartial.id;
|
||||||
const options = queryKeys.metricsGetMetric(metricId);
|
const options = queryKeys.metricsGetMetric(metricId);
|
||||||
|
@ -163,17 +166,41 @@ export const useUpdateMetric = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (prevMetric && newMetric) {
|
if (prevMetric && newMetric) {
|
||||||
const changedValues = prepareMetricUpdateMetric(newMetric, prevMetric);
|
queryClient.setQueryData(options.queryKey, newMetric);
|
||||||
if (changedValues) {
|
}
|
||||||
return saveMetric(changedValues);
|
|
||||||
}
|
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) {
|
||||||
|
saveMetric(changedValues);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve(newMetric!);
|
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 = () => {
|
export const useDeleteMetric = () => {
|
||||||
|
|
|
@ -16,12 +16,10 @@ import { useDebounceFn, useMemoizedFn } from '@/hooks';
|
||||||
import { prepareMetricUpdateMetric, resolveEmptyMetric } from '@/lib/metrics';
|
import { prepareMetricUpdateMetric, resolveEmptyMetric } from '@/lib/metrics';
|
||||||
import { create } from 'mutative';
|
import { create } from 'mutative';
|
||||||
import { ShareRole } from '@/api/asset_interfaces/share';
|
import { ShareRole } from '@/api/asset_interfaces/share';
|
||||||
import { useUpdateMetric } from '@/api/buster_rest/metrics';
|
|
||||||
|
|
||||||
const useBusterMetrics = () => {
|
const useBusterMetrics = () => {
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const { metricId: selectedMetricId } = useParams<{ metricId: string }>();
|
const { metricId: selectedMetricId } = useParams<{ metricId: string }>();
|
||||||
const { mutateAsync: updateMetricMutation } = useUpdateMetric();
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const getMetricId = useMemoizedFn((metricId?: string): string => {
|
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';
|
'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 {
|
interface DebounceOptions {
|
||||||
wait?: number;
|
wait?: number;
|
||||||
|
@ -8,79 +11,38 @@ interface DebounceOptions {
|
||||||
leading?: boolean;
|
leading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDebounceFn<T extends (...args: any[]) => any>(
|
type noop = (...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);
|
|
||||||
|
|
||||||
// Update the function ref when fn changes
|
export function useDebounceFn<T extends noop>(fn: T, options?: DebounceOptions) {
|
||||||
useEffect(() => {
|
const fnRef = useLatest(fn);
|
||||||
fnRef.current = fn;
|
|
||||||
}, [fn]);
|
|
||||||
|
|
||||||
const cancel = useCallback(() => {
|
const wait = options?.wait ?? 1000;
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
}
|
|
||||||
if (maxTimeoutRef.current) {
|
|
||||||
clearTimeout(maxTimeoutRef.current);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const run = useCallback(
|
const debounced = useMemo(
|
||||||
(...args: Parameters<T>) => {
|
() =>
|
||||||
argsRef.current = args;
|
debounce(
|
||||||
const now = Date.now();
|
(...args: Parameters<T>): ReturnType<T> => {
|
||||||
|
return fnRef.current(...args);
|
||||||
if (leading && !timeoutRef.current) {
|
},
|
||||||
fnRef.current(...args);
|
wait,
|
||||||
lastCallTime.current = now;
|
options
|
||||||
}
|
),
|
||||||
|
[]
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[wait, maxWait, leading, cancel]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up timeouts on unmount
|
useUnmount(() => {
|
||||||
useEffect(() => {
|
debounced.cancel();
|
||||||
return () => cancel();
|
});
|
||||||
}, [cancel]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run,
|
run: debounced,
|
||||||
cancel
|
cancel: debounced.cancel,
|
||||||
|
flush: debounced.flush
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default useDebounceFn;
|
||||||
|
|
||||||
export function useDebounce<T>(value: T, options: DebounceOptions = {}) {
|
export function useDebounce<T>(value: T, options: DebounceOptions = {}) {
|
||||||
const { wait = 1000, maxWait } = options;
|
const { wait = 1000, maxWait } = options;
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
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