prefetcher for version history panel

This commit is contained in:
Nate Kelley 2025-04-16 10:46:20 -06:00
parent 441ec1df40
commit 73768eebc9
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 145 additions and 35 deletions

View File

@ -118,6 +118,30 @@ export const useGetMetric = <TData = IBusterMetric>(
}); });
}; };
export const prefetchGetMetricClient = async (
{ id, versionNumber }: { id: string; versionNumber: number | undefined },
queryClient: QueryClient
) => {
const options = metricsQueryKeys.metricsGetMetric(id, versionNumber);
const existingData = queryClient.getQueryData(options.queryKey);
if (!existingData) {
await queryClient.prefetchQuery({
...options,
queryFn: async () => {
const result = await getMetric({ id, version_number: versionNumber });
return upgradeMetricToIMetric(result, null);
}
});
}
};
export const usePrefetchGetMetricClient = () => {
const queryClient = useQueryClient();
return useMemoizedFn(({ id, versionNumber }: { id: string; versionNumber: number | undefined }) =>
prefetchGetMetricClient({ id, versionNumber }, queryClient)
);
};
export const useGetMetricsList = ( export const useGetMetricsList = (
params: Omit<Parameters<typeof listMetrics>[0], 'page_token' | 'page_size'> params: Omit<Parameters<typeof listMetrics>[0], 'page_token' | 'page_size'>
) => { ) => {
@ -206,6 +230,13 @@ export const prefetchGetMetricDataClient = async (
} }
}; };
export const usePrefetchGetMetricDataClient = () => {
const queryClient = useQueryClient();
return useMemoizedFn(({ id, versionNumber }: { id: string; versionNumber: number | undefined }) =>
prefetchGetMetricDataClient({ id, version_number: versionNumber }, queryClient)
);
};
/** /**
* This is a mutation that saves a metric to the server. * This is a mutation that saves a metric to the server.
* It will simply use the params passed in and not do any special logic. * It will simply use the params passed in and not do any special logic.

View File

@ -8,17 +8,24 @@ import { cn } from '@/lib/classMerge';
import { timeFromNow, timeout } from '@/lib'; import { timeFromNow, timeout } from '@/lib';
import { AppPageLayout } from '@/components/ui/layouts'; import { AppPageLayout } from '@/components/ui/layouts';
import { useListVersionHistories } from './useListVersionHistories'; import { useListVersionHistories } from './useListVersionHistories';
import { useMount } from '@/hooks'; import { useMemoizedFn, useMount } from '@/hooks';
import { AppTooltip } from '@/components/ui/tooltip'; import { AppTooltip } from '@/components/ui/tooltip';
import Link from 'next/link'; import Link from 'next/link';
import { useGetFileLink } from '@/context/Assets/useGetFileLink'; import { useGetFileLink } from '@/context/Assets/useGetFileLink';
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout'; import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
import { useRouter } from 'next/navigation';
import { useCallback } from 'react';
export const VersionHistoryPanel = React.memo( export const VersionHistoryPanel = React.memo(
({ assetId, type }: { assetId: string; type: 'metric' | 'dashboard' }) => { ({ assetId, type }: { assetId: string; type: 'metric' | 'dashboard' }) => {
const chatId = useChatLayoutContextSelector((x) => x.chatId); const chatId = useChatLayoutContextSelector((x) => x.chatId);
const { listItems, currentVersionNumber, selectedQueryVersion, onClickRestoreVersion } = const {
useListVersionHistories({ listItems,
onPrefetchAsset,
currentVersionNumber,
selectedQueryVersion,
onClickRestoreVersion
} = useListVersionHistories({
assetId, assetId,
type type
}); });
@ -51,6 +58,7 @@ export const VersionHistoryPanel = React.memo(
<ListItem <ListItem
key={item.version_number} key={item.version_number}
{...item} {...item}
onPrefetchAsset={onPrefetchAsset}
selected={item.version_number === selectedQueryVersion} selected={item.version_number === selectedQueryVersion}
showRestoreButton={item.version_number !== currentVersionNumber} showRestoreButton={item.version_number !== currentVersionNumber}
onClickRestoreVersion={onClickRestoreVersion} onClickRestoreVersion={onClickRestoreVersion}
@ -78,18 +86,37 @@ const ListItem = React.memo(
selected, selected,
showRestoreButton, showRestoreButton,
link, link,
onClickRestoreVersion onClickRestoreVersion,
onPrefetchAsset
}: { }: {
version_number: number; version_number: number;
updated_at: string; updated_at: string;
selected: boolean; selected: boolean;
showRestoreButton: boolean; showRestoreButton: boolean;
onClickRestoreVersion: (versionNumber: number) => void; onClickRestoreVersion: (versionNumber: number) => void;
onPrefetchAsset: (versionNumber: number, link: string) => Promise<void>;
link: string; link: string;
}) => { }) => {
const routePrefetchTimeoutRef = useRef<NodeJS.Timeout>();
const onHoverLink = useMemoizedFn(() => {
// Prefetch route after 50ms
routePrefetchTimeoutRef.current = setTimeout(() => {
onPrefetchAsset(version_number, link);
}, 125);
});
const onHoverEnd = useCallback(() => {
if (routePrefetchTimeoutRef.current) {
clearTimeout(routePrefetchTimeoutRef.current);
}
}, []);
return ( return (
<Link prefetch={false} href={link}> <Link prefetch={false} href={link}>
<div <div
onMouseEnter={onHoverLink}
onMouseLeave={onHoverEnd}
className={cn( className={cn(
'group hover:bg-item-hover flex cursor-pointer items-center justify-between space-x-2 rounded px-2.5 py-1.5', 'group hover:bg-item-hover flex cursor-pointer items-center justify-between space-x-2 rounded px-2.5 py-1.5',
selected && 'bg-item-select hover:bg-item-select selected-version' selected && 'bg-item-select hover:bg-item-select selected-version'

View File

@ -1,7 +1,11 @@
'use client'; 'use client';
import { useGetDashboard, useUpdateDashboard } from '@/api/buster_rest/dashboards'; import { useGetDashboard, useUpdateDashboard } from '@/api/buster_rest/dashboards';
import { useGetMetric, useSaveMetric } from '@/api/buster_rest/metrics'; import {
useGetMetric,
usePrefetchGetMetricDataClient,
useSaveMetric
} from '@/api/buster_rest/metrics';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout'; import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
@ -9,6 +13,8 @@ import { useCloseVersionHistory } from '@/layouts/ChatLayout/FileContainer/FileC
import { BusterRoutes, createBusterRoute } from '@/routes'; import { BusterRoutes, createBusterRoute } from '@/routes';
import last from 'lodash/last'; import last from 'lodash/last';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { usePrefetchGetMetricClient } from '@/api/buster_rest/metrics';
import { useRouter } from 'next/navigation';
export const useListVersionHistories = ({ export const useListVersionHistories = ({
assetId, assetId,
@ -17,24 +23,27 @@ export const useListVersionHistories = ({
assetId: string; assetId: string;
type: 'metric' | 'dashboard'; type: 'metric' | 'dashboard';
}) => { }) => {
const router = useRouter();
const { onCloseVersionHistory } = useCloseVersionHistory(); const { onCloseVersionHistory } = useCloseVersionHistory();
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage); const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
const { const {
dashboardVersions, versions: dashboardVersions,
selectedQueryVersion: dashboardSelectedQueryVersion, selectedQueryVersion: dashboardSelectedQueryVersion,
currentVersionNumber: dashboardCurrentVersionNumber, currentVersionNumber: dashboardCurrentVersionNumber,
onRestoreVersion: onRestoreDashboardVersion, onRestoreVersion: onRestoreDashboardVersion,
isSavingDashboard isSaving: isSavingDashboard,
onPrefetchAsset: onPrefetchDashboardAsset
} = useListDashboardVersions({ } = useListDashboardVersions({
assetId, assetId,
type type
}); });
const { const {
metricVersions, versions: metricVersions,
selectedQueryVersion: metricSelectedQueryVersion, selectedQueryVersion: metricSelectedQueryVersion,
currentVersionNumber: metricCurrentVersionNumber, currentVersionNumber: metricCurrentVersionNumber,
onRestoreVersion: onRestoreMetricVersion, onRestoreVersion: onRestoreMetricVersion,
isSavingMetric isSaving: isSavingMetric,
onPrefetchAsset: onPrefetchMetricAsset
} = useListMetricVersions({ } = useListMetricVersions({
assetId, assetId,
type type
@ -82,13 +91,24 @@ export const useListVersionHistories = ({
} }
); );
const onPrefetchAsset = useMemoizedFn(async (versionNumber: number, link: string) => {
router.prefetch(link);
if (type === 'metric') {
await onPrefetchMetricAsset(versionNumber);
} else {
await onPrefetchDashboardAsset(versionNumber);
}
});
return useMemo(() => { return useMemo(() => {
return { return {
listItems, listItems,
currentVersionNumber, currentVersionNumber,
selectedQueryVersion, selectedQueryVersion,
onClickRestoreVersion, onClickRestoreVersion,
isRestoringVersion: isSavingDashboard || isSavingMetric isRestoringVersion: isSavingDashboard || isSavingMetric,
onPrefetchAsset
}; };
}, [ }, [
listItems, listItems,
@ -96,19 +116,34 @@ export const useListVersionHistories = ({
selectedQueryVersion, selectedQueryVersion,
onClickRestoreVersion, onClickRestoreVersion,
isSavingDashboard, isSavingDashboard,
isSavingMetric isSavingMetric,
onPrefetchAsset
]); ]);
}; };
type UseListVersionReturn = {
versions:
| {
version_number: number;
updated_at: string;
}[]
| undefined;
selectedQueryVersion: number | undefined;
onRestoreVersion: (versionNumber: number) => Promise<unknown>;
currentVersionNumber: number | undefined;
isSaving: boolean;
onPrefetchAsset: (versionNumber: number) => Promise<void>;
};
const useListDashboardVersions = ({ const useListDashboardVersions = ({
assetId, assetId,
type type
}: { }: {
assetId: string; assetId: string;
type: 'metric' | 'dashboard'; type: 'metric' | 'dashboard';
}) => { }): UseListVersionReturn => {
const dashboardVersionNumber = useChatLayoutContextSelector((x) => x.dashboardVersionNumber); const dashboardVersionNumber = useChatLayoutContextSelector((x) => x.dashboardVersionNumber);
const { mutateAsync: updateDashboard, isPending: isSavingDashboard } = useUpdateDashboard({ const { mutateAsync: updateDashboard, isPending: isSaving } = useUpdateDashboard({
saveToServer: true, saveToServer: true,
updateVersion: true updateVersion: true
}); });
@ -125,13 +160,13 @@ const useListDashboardVersions = ({
} }
); );
const dashboardVersions = dashboardData?.versions; const versions = dashboardData?.versions;
const currentVersionNumber = dashboardData?.version_number; const currentVersionNumber = dashboardData?.version_number;
const selectedQueryVersion = useMemo(() => { const selectedQueryVersion = useMemo(() => {
if (dashboardVersionNumber) return dashboardVersionNumber; if (dashboardVersionNumber) return dashboardVersionNumber;
return last(dashboardVersions)?.version_number; return last(versions)?.version_number;
}, [dashboardVersions, dashboardVersionNumber]); }, [versions, dashboardVersionNumber]);
const onRestoreVersion = useMemoizedFn(async (versionNumber: number) => { const onRestoreVersion = useMemoizedFn(async (versionNumber: number) => {
await updateDashboard({ await updateDashboard({
@ -140,20 +175,26 @@ const useListDashboardVersions = ({
}); });
}); });
const onPrefetchAsset = useMemoizedFn(async (versionNumber: number) => {
//
});
return useMemo(() => { return useMemo(() => {
return { return {
dashboardVersions, versions,
selectedQueryVersion, selectedQueryVersion,
onRestoreVersion, onRestoreVersion,
currentVersionNumber, currentVersionNumber,
isSavingDashboard isSaving,
onPrefetchAsset
}; };
}, [ }, [
dashboardVersions, versions,
currentVersionNumber, currentVersionNumber,
onRestoreVersion, onRestoreVersion,
selectedQueryVersion, selectedQueryVersion,
isSavingDashboard isSaving,
onPrefetchAsset
]); ]);
}; };
@ -163,10 +204,12 @@ const useListMetricVersions = ({
}: { }: {
assetId: string; assetId: string;
type: 'metric' | 'dashboard'; type: 'metric' | 'dashboard';
}) => { }): UseListVersionReturn => {
const { mutateAsync: updateMetric, isPending: isSavingMetric } = useSaveMetric({ const { mutateAsync: updateMetric, isPending: isSaving } = useSaveMetric({
updateOnSave: true updateOnSave: true
}); });
const prefetchGetMetric = usePrefetchGetMetricClient();
const prefetchGetMetricData = usePrefetchGetMetricDataClient();
const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber); const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber);
@ -181,9 +224,14 @@ const useListMetricVersions = ({
}) })
} }
); );
const metricVersions = metric?.versions; const versions = metric?.versions;
const currentVersionNumber = metricVersionNumber || metric?.version_number; const currentVersionNumber = metricVersionNumber || metric?.version_number;
const selectedQueryVersion = useMemo(() => {
if (metricVersionNumber) return metricVersionNumber;
return last(versions)?.version_number;
}, [versions, metricVersionNumber]);
const onRestoreVersion = useMemoizedFn(async (versionNumber: number) => { const onRestoreVersion = useMemoizedFn(async (versionNumber: number) => {
await updateMetric({ await updateMetric({
id: assetId, id: assetId,
@ -191,24 +239,28 @@ const useListMetricVersions = ({
}); });
}); });
const selectedQueryVersion = useMemo(() => { const onPrefetchAsset = useMemoizedFn(async (versionNumber: number) => {
if (metricVersionNumber) return metricVersionNumber; await Promise.all([
return last(metricVersions)?.version_number; prefetchGetMetric({ id: assetId, versionNumber }),
}, [metricVersions, metricVersionNumber]); prefetchGetMetricData({ id: assetId, versionNumber })
]);
});
return useMemo(() => { return useMemo(() => {
return { return {
metricVersions, versions,
selectedQueryVersion, selectedQueryVersion,
onRestoreVersion, onRestoreVersion,
currentVersionNumber, currentVersionNumber,
isSavingMetric isSaving,
onPrefetchAsset
}; };
}, [ }, [
metricVersions, versions,
currentVersionNumber,
onRestoreVersion, onRestoreVersion,
selectedQueryVersion, selectedQueryVersion,
currentVersionNumber, isSaving,
isSavingMetric onPrefetchAsset
]); ]);
}; };