finalized some prefetch logic

This commit is contained in:
Nate Kelley 2025-04-11 12:43:19 -06:00
parent 06b4236388
commit 17748b1ddb
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
22 changed files with 167 additions and 101 deletions

View File

@ -84,7 +84,10 @@ export const useGetChat = <TData = IBusterChat>(
if (lastMessage) {
Object.values(lastMessage.response_messages).forEach((responseMessage) => {
if (responseMessage.type === 'file' && responseMessage.file_type === 'metric') {
prefetchGetMetricDataClient({ id: responseMessage.id }, queryClient);
prefetchGetMetricDataClient(
{ id: responseMessage.id, version_number: responseMessage.version_number },
queryClient
);
}
});
}

View File

@ -61,7 +61,10 @@ const useGetDashboardAndInitializeMetrics = () => {
const prevMetric = queryClient.getQueryData(queryKeys.metricsGetMetric(metric.id).queryKey);
const upgradedMetric = upgradeMetricToIMetric(metric, prevMetric);
queryClient.setQueryData(queryKeys.metricsGetMetric(metric.id).queryKey, upgradedMetric);
prefetchGetMetricDataClient({ id: metric.id }, queryClient);
prefetchGetMetricDataClient(
{ id: metric.id, version_number: metric.version_number },
queryClient
);
}
});

View File

@ -34,6 +34,31 @@ import {
import { useParams, useSearchParams } from 'next/navigation';
import { useOriginalMetricStore } from '@/context/Metrics/useOriginalMetricStore';
import { RustApiError } from '../../buster_rest/errors';
import last from 'lodash/last';
const useGetMetricVersionNumber = ({
versionNumber: versionNumberProp
}: {
versionNumber?: number | null; //if null it will not use a params from the query params
}) => {
const { versionNumber: versionNumberPathParam, metricId: metricIdPathParam } = useParams() as {
versionNumber: string | undefined;
metricId: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('metric_version_number');
const versionNumberFromParams = metricIdPathParam
? versionNumberQueryParam || versionNumberPathParam
: undefined;
const versionNumber = useMemo(() => {
if (versionNumberProp === null) return undefined;
return versionNumberProp || versionNumberFromParams
? parseInt(versionNumberFromParams!)
: undefined;
}, [versionNumberProp, versionNumberFromParams]);
return versionNumber;
};
/**
* This is a hook that will use the version number from the URL params if it exists.
@ -54,21 +79,7 @@ export const useGetMetric = <TData = IBusterMetric>(
const setAssetPasswordError = useBusterAssetsContextSelector((x) => x.setAssetPasswordError);
const { password } = getAssetPassword(id!);
const { versionNumber: versionNumberPathParam, metricId: metricIdPathParam } = useParams() as {
versionNumber: string | undefined;
metricId: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('versionNumber');
const versionNumberFromParams = metricIdPathParam
? versionNumberQueryParam || versionNumberPathParam
: undefined;
const versionNumber = useMemo(() => {
if (versionNumberProp === null) return undefined;
return versionNumberProp || versionNumberFromParams
? parseInt(versionNumberFromParams!)
: undefined;
}, [versionNumberProp, versionNumberFromParams]);
const versionNumber = useGetMetricVersionNumber({ versionNumber: versionNumberProp });
const options = useMemo(() => {
return metricsQueryKeys.metricsGetMetric(id!, versionNumber);
@ -78,7 +89,10 @@ export const useGetMetric = <TData = IBusterMetric>(
const result = await getMetric({ id: id!, password, version_number: versionNumber });
const oldMetric = queryClient.getQueryData(options.queryKey);
const updatedMetric = upgradeMetricToIMetric(result, oldMetric || null);
setOriginalMetric(updatedMetric);
const isLatestVersion =
updatedMetric.version_number === last(updatedMetric.versions)?.version_number;
if (isLatestVersion) setOriginalMetric(updatedMetric);
return updatedMetric;
});
@ -128,28 +142,13 @@ export const useGetMetricData = <TData = IBusterMetricData>(
) => {
const getAssetPassword = useBusterAssetsContextSelector((x) => x.getAssetPassword);
const { password } = getAssetPassword(id!);
const versionNumber = useGetMetricVersionNumber({ versionNumber: versionNumberProp });
const {
isFetched: isFetchedMetric,
error: errorMetric,
isError: isErrorMetric,
dataUpdatedAt,
data: fetchedMetricData
} = useGetMetric({ id }, { select: (x) => x.id });
const { versionNumber: versionNumberPathParam, metricId: metricIdPathParam } = useParams() as {
versionNumber: string | undefined;
metricId: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('versionNumber');
const versionNumberFromParams = metricIdPathParam
? versionNumberQueryParam || versionNumberPathParam
: undefined;
const versionNumber = useMemo(() => {
if (versionNumberProp === null) return undefined;
return versionNumberProp || versionNumberFromParams
? parseInt(versionNumberFromParams!)
: undefined;
}, [versionNumberProp, versionNumberFromParams]);
} = useGetMetric({ id, versionNumber }, { select: (x) => x.id });
const queryFn = useMemoizedFn(() => {
return getMetricData({ id: id!, version_number: versionNumber, password });
});
@ -158,7 +157,7 @@ export const useGetMetricData = <TData = IBusterMetricData>(
...metricsQueryKeys.metricsGetData(id!, versionNumber),
queryFn,
enabled: () => {
return !!id && isFetchedMetric && !errorMetric && !!fetchedMetricData;
return !!id && isFetchedMetric && !isErrorMetric && !!fetchedMetricData && !!dataUpdatedAt;
},
select: params?.select,
...params
@ -166,7 +165,7 @@ export const useGetMetricData = <TData = IBusterMetricData>(
};
export const prefetchGetMetricDataClient = async (
{ id, version_number }: { id: string; version_number?: number },
{ id, version_number }: { id: string; version_number: number | undefined },
queryClient: QueryClient
) => {
const options = metricsQueryKeys.metricsGetData(id, version_number);

View File

@ -83,7 +83,10 @@ export const BusterChart: React.FC<BusterChartProps> = React.memo(
});
const SwitchComponent = useMemoizedFn(() => {
if (loading || error || !isMounted) {
//chartjs need the parent to be mounted to render the chart. It is intermitent when it throws when the parent is not mounted.
if (!isMounted && selectedChartType !== ChartType.Table) return null;
if (loading || error) {
return <PreparingYourRequestLoader error={error} />;
}

View File

@ -11,7 +11,8 @@ export const PreparingYourRequestLoader: React.FC<{
useShimmer?: boolean;
}> = ({ className = '', text = 'Processing your request...', error, useShimmer = true }) => {
return (
<div className={`flex h-full w-full items-center justify-center space-x-1.5 ${className}`}>
<div
className={`flex h-full min-h-24 w-full items-center justify-center space-x-1.5 ${className}`}>
{error || useShimmer === false ? (
<span className="text-text-tertiary flex items-center text-center">{error || text}</span>
) : (

View File

@ -71,7 +71,10 @@ export const useChatStreamMessage = () => {
if (lastMessage?.response_message_ids) {
Object.values(lastMessage.response_messages).forEach((responseMessage) => {
if (responseMessage.type === 'file' && responseMessage.file_type === 'metric') {
prefetchGetMetricDataClient({ id: responseMessage.id }, queryClient);
prefetchGetMetricDataClient(
{ id: responseMessage.id, version_number: responseMessage.version_number },
queryClient
);
}
});
}

View File

@ -23,7 +23,8 @@ export const useIsMetricReadOnly = ({
enabled: false,
select: (x) => ({
permission: x.permission,
versions: x.versions
versions: x.versions,
version_number: x.version_number
})
}
);

View File

@ -2,11 +2,7 @@
import type { IBusterMetric } from '@/api/asset_interfaces/metric';
import { create } from 'zustand';
import { compareObjectsByKeys } from '@/lib/objects';
import { useGetMetric } from '@/api/buster_rest/metrics/queryRequests';
import { useMemoizedFn, useMount } from '@/hooks';
import { useQueryClient } from '@tanstack/react-query';
import { metricsQueryKeys } from '@/api/query_keys/metric';
import { useMount } from '@/hooks';
type OriginalMetricStore = {
originalMetrics: Record<string, IBusterMetric>;

View File

@ -24,12 +24,11 @@ export const DashboardViewDashboardController: React.FC<{
} = useGetDashboard({ id: dashboardId });
const { mutateAsync: onUpdateDashboardConfig } = useUpdateDashboardConfig();
const isVersionHistoryMode = useChatLayoutContextSelector((x) => x.isVersionHistoryMode);
const onOpenAddContentModal = useDashboardContentStore((x) => x.onOpenAddContentModal);
const metrics = dashboardResponse?.metrics;
const dashboard = dashboardResponse?.dashboard;
const { isReadOnly } = useIsDashboardReadOnly({
const { isReadOnly, isViewingOldVersion, isVersionHistoryMode } = useIsDashboardReadOnly({
dashboardId,
readOnly: readOnlyProp
});
@ -65,7 +64,9 @@ export const DashboardViewDashboardController: React.FC<{
readOnly={isReadOnly}
/>
{!isVersionHistoryMode && <DashboardSavePopup dashboardId={dashboardId} />}
{!isVersionHistoryMode && !isViewingOldVersion && (
<DashboardSavePopup dashboardId={dashboardId} />
)}
</div>
</ScrollArea>
);

View File

@ -22,7 +22,7 @@ export const MetricStylingApp: React.FC<{
MetricStylingAppSegments.VISUALIZE
);
const { data: chartConfig } = useGetMetric({ id: metricId }, { select: (x) => x.chart_config });
const { data: metricData } = useGetMetricData({ id: metricId });
const { data: metricData } = useGetMetricData({ id: metricId }, { enabled: false });
if (!chartConfig) return null;

View File

@ -33,7 +33,9 @@ export const MetricViewChart: React.FC<{
time_frame,
permission,
evaluation_score,
evaluation_summary
evaluation_summary,
version_number,
versions
}) => ({
name,
description,
@ -41,7 +43,9 @@ export const MetricViewChart: React.FC<{
permission,
evaluation_score,
evaluation_summary,
chart_config
chart_config,
version_number,
versions
})
}
);
@ -49,7 +53,7 @@ export const MetricViewChart: React.FC<{
data: metricData,
isFetched: isFetchedMetricData,
error: metricDataError
} = useGetMetricData({ id: metricId });
} = useGetMetricData({ id: metricId }, { enabled: false });
const { mutate: updateMetric } = useUpdateMetric({
saveToServer: false
@ -57,7 +61,7 @@ export const MetricViewChart: React.FC<{
const { name, description, time_frame, evaluation_score, evaluation_summary } = metric || {};
const isTable = metric?.chart_config.selectedChartType === ChartType.Table;
const { isReadOnly, isVersionHistoryMode } = useIsMetricReadOnly({
const { isReadOnly, isVersionHistoryMode, isViewingOldVersion } = useIsMetricReadOnly({
metricId,
readOnly: readOnlyProp
});
@ -110,7 +114,9 @@ export const MetricViewChart: React.FC<{
/>
</AnimatePresenceWrapper>
{!isVersionHistoryMode && <MetricSaveFilePopup metricId={metricId} />}
{!isVersionHistoryMode && !isViewingOldVersion && (
<MetricSaveFilePopup metricId={metricId} />
)}
</div>
);
}

View File

@ -38,7 +38,10 @@ export const MetricViewResults: React.FC<{ metricId: string }> = React.memo(({ m
})
}
);
const { data: metricData, isFetched: isFetchedInitialData } = useGetMetricData({ id: metricId });
const { data: metricData, isFetched: isFetchedInitialData } = useGetMetricData(
{ id: metricId },
{ enabled: false }
);
const [sql, setSQL] = useState(metric?.sql || '');

View File

@ -6,6 +6,7 @@ import { AppPasswordAccess } from '@/controllers/AppPasswordAccess';
import { AppNoPageAccess } from '@/controllers/AppNoPageAccess';
import { useGetAsset } from './useGetAsset';
import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader';
import { useParams, useSearchParams } from 'next/navigation';
export type AppAssetCheckLayoutProps = {
assetId: string;

View File

@ -3,6 +3,8 @@
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import { useGetDashboard } from '@/api/buster_rest/dashboards';
import { RustApiError } from '@/api/buster_rest/errors';
import { useSearchParams } from 'next/navigation';
import { useMemo } from 'react';
interface BaseGetAssetProps {
assetId: string;
@ -15,6 +17,7 @@ interface MetricAssetProps extends BaseGetAssetProps {
interface DashboardAssetProps extends BaseGetAssetProps {
type: 'dashboard';
versionNumber?: number;
}
type UseGetAssetProps = MetricAssetProps | DashboardAssetProps;
@ -29,47 +32,55 @@ type UseGetAssetReturn<T extends UseGetAssetProps> = {
};
export const useGetAsset = (props: UseGetAssetProps): UseGetAssetReturn<typeof props> => {
const searchParams = useSearchParams();
const metricVersionNumber = searchParams.get('metric_version_number');
const dashboardVersionNumber = searchParams.get('dashboard_version_number');
const queryParamVersionNumber: number | undefined = useMemo(() => {
if (props.type === 'metric' && metricVersionNumber) {
return parseInt(metricVersionNumber);
}
if (props.type === 'dashboard' && dashboardVersionNumber) {
return parseInt(dashboardVersionNumber);
}
return undefined;
}, [props.type, metricVersionNumber, dashboardVersionNumber]);
const versionNumber: number | undefined = useMemo(() => {
if (props.type === 'metric') {
if (props.versionNumber) return props.versionNumber;
if (queryParamVersionNumber) return queryParamVersionNumber;
}
if (props.type === 'dashboard') {
if (props.versionNumber) return props.versionNumber;
if (queryParamVersionNumber) return queryParamVersionNumber;
}
return undefined;
}, [props, queryParamVersionNumber]);
//metric
const {
error: errorMetric,
isError: isErrorMetric,
dataUpdatedAt,
isFetched: isFetchedMetric
} = useGetMetric(
const { error: errorMetric, isFetched: isFetchedMetric } = useGetMetric(
{
id: props.assetId,
versionNumber: props.type === 'metric' ? props.versionNumber : undefined
id: props.type === 'metric' ? props.assetId : undefined,
versionNumber
},
{
enabled: props.type === 'metric' && !!props.assetId
}
);
const { isFetched: isFetchedMetricData } = useGetMetricData(
{
id: props.assetId,
versionNumber: props.type === 'metric' ? props.versionNumber : undefined
},
{
enabled:
props.type === 'metric' &&
!!props.assetId &&
isFetchedMetric &&
!!dataUpdatedAt && //This is a hack to prevent the query from being run when the asset is not fetched.
!isErrorMetric
}
{ enabled: props.type === 'metric' && !!props.assetId }
);
const { isFetched: isFetchedMetricData } = useGetMetricData({
id: props.assetId,
versionNumber
});
//dashboard
const {
isFetched: isFetchedDashboard,
error: errorDashboard,
isError: isErrorDashboard
} = useGetDashboard(
{
id: props.type === 'dashboard' ? props.assetId : undefined
},
{ enabled: props.type === 'dashboard' && !!props.assetId }
);
} = useGetDashboard({
id: props.type === 'dashboard' ? props.assetId : undefined,
versionNumber
});
const { hasAccess, passwordRequired, isPublic } = getAssetAccess({
error: props.type === 'metric' ? errorMetric : errorDashboard

View File

@ -30,9 +30,14 @@ export const ChatContent: React.FC<{}> = React.memo(() => {
return (
<>
<div className="mb-40 flex h-full w-full flex-col overflow-hidden">
{chatMessageIds?.map((messageId) => (
{chatMessageIds?.map((messageId, index) => (
<div key={messageId} className={autoClass}>
<ChatMessageBlock key={messageId} messageId={messageId} chatId={chatId || ''} />
<ChatMessageBlock
key={messageId}
messageId={messageId}
chatId={chatId || ''}
messageIndex={index}
/>
</div>
))}
</div>

View File

@ -8,7 +8,8 @@ import { useGetChatMessage } from '@/api/buster_rest/chats';
export const ChatMessageBlock: React.FC<{
messageId: string;
chatId: string;
}> = React.memo(({ messageId, chatId }) => {
messageIndex: number;
}> = React.memo(({ messageId, chatId, messageIndex }) => {
const messageExists = useGetChatMessage(messageId, (message) => message?.id);
const requestMessage = useGetChatMessage(messageId, (message) => message?.request_message);
const isCompletedStream = useGetChatMessage(messageId, (x) => x?.isCompletedStream);
@ -29,6 +30,7 @@ export const ChatMessageBlock: React.FC<{
isCompletedStream={isCompletedStream!}
messageId={messageId}
chatId={chatId}
messageIndex={messageIndex}
/>
</div>
);

View File

@ -34,7 +34,7 @@ export const ChatResponseMessage_File: React.FC<ChatResponseMessageProps> = Reac
const onLinkClick = useMemoizedFn(() => {
if (isSelectedFile) onSetSelectedFile(null);
onSetSelectedFile({ id, type: file_type });
onSetSelectedFile({ id, type: file_type, versionNumber: responseMessage.version_number });
});
useMount(() => {

View File

@ -9,17 +9,19 @@ interface ChatResponseMessagesProps {
isCompletedStream: boolean;
messageId: string;
chatId: string;
messageIndex: number;
}
export const ChatResponseMessages: React.FC<ChatResponseMessagesProps> = React.memo(
({ chatId, isCompletedStream, messageId }) => {
({ chatId, isCompletedStream, messageId, messageIndex }) => {
const responseMessageIds = useGetChatMessage(messageId, (x) => x?.response_message_ids || [])!;
const lastReasoningMessageId = useGetChatMessage(
messageId,
(x) => x?.reasoning_message_ids?.[x.reasoning_message_ids.length - 1]
);
const finalReasoningMessage = useGetChatMessage(messageId, (x) => x?.final_reasoning_message);
const showReasoningMessage = !!lastReasoningMessageId || !isCompletedStream;
const showReasoningMessage =
messageIndex === 0 ? !!lastReasoningMessageId || !isCompletedStream : true;
return (
<MessageContainer className="flex w-full flex-col space-y-3 overflow-hidden">

View File

@ -4,19 +4,30 @@ import { useGetChatParams } from '../useGetChatParams';
export const createSelectedFile = (
params: ReturnType<typeof useGetChatParams>
): SelectedFile | null => {
const { metricId, collectionId, datasetId, dashboardId, chatId, messageId } = params;
const {
metricId,
collectionId,
datasetId,
dashboardId,
chatId,
messageId,
metricVersionNumber,
dashboardVersionNumber
} = params;
if (metricId) {
return {
id: metricId,
type: 'metric'
type: 'metric',
versionNumber: metricVersionNumber
};
}
if (dashboardId) {
return {
id: dashboardId,
type: 'dashboard'
type: 'dashboard',
versionNumber: dashboardVersionNumber
};
}

View File

@ -31,8 +31,7 @@ export const useSelectedFile = ({
* @param file
*/
const onSetSelectedFile = useMemoizedFn(async (file: SelectedFile | null) => {
const handleFileCollapse =
!file || (file?.id === selectedFile?.id && !appSplitterRef.current?.isSideClosed('right'));
const handleFileCollapse = shouldCloseSplitter(file, selectedFile, appSplitterRef);
if (file && chatParams.chatId) {
const link = createChatAssetRoute({
@ -61,3 +60,18 @@ export const useSelectedFile = ({
};
export type SelectedFileParams = ReturnType<typeof useSelectedFile>;
const shouldCloseSplitter = (
file: SelectedFile | null,
selectedFile: SelectedFile | null,
appSplitterRef: React.RefObject<AppSplitterRef | null>
) => {
if (!file) return false;
if (file?.id === selectedFile?.id && !appSplitterRef.current?.isSideClosed('right')) {
if (!!file?.versionNumber && !!selectedFile?.versionNumber) {
return file.versionNumber === selectedFile.versionNumber;
}
return true;
}
return false;
};

View File

@ -403,7 +403,7 @@ const useSQLEditorSelectMenu = () => {
const useDownloadCSVSelectMenu = ({ metricId }: { metricId: string }) => {
const [isDownloading, setIsDownloading] = useState(false);
const { data: metricData } = useGetMetricData({ id: metricId });
const { data: metricData } = useGetMetricData({ id: metricId }, { enabled: false });
const { data: name } = useGetMetric({ id: metricId }, { select: (x) => x.name });
return useMemo(

View File

@ -3,6 +3,7 @@ import type { FileType } from '@/api/asset_interfaces';
export type SelectedFile = {
id: string;
type: FileType;
versionNumber?: number;
};
export type ChatLayoutView = 'chat' | 'file' | 'both';