error for metric

This commit is contained in:
Nate Kelley 2025-03-25 16:28:07 -06:00
parent a6e12cb06b
commit 0ebcc6ffab
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 126 additions and 35 deletions

View File

@ -1,6 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { QueryClient } from '@tanstack/react-query';
import { useDebounceFn, useMemoizedFn, useMount, useUnmount } from '@/hooks';
import { useDebounceFn, useMemoizedFn } from '@/hooks';
import {
deleteMetrics,
duplicateMetric,
@ -26,21 +26,32 @@ import {
useAddAssetToCollection,
useRemoveAssetFromCollection
} from '../collections/queryRequests';
import debounce from 'lodash/debounce';
import { useSearchParams } from 'next/navigation';
/**
* This is a hook that will use the version number from the URL params if it exists.
*/
export const useGetMetric = <TData = IBusterMetric>(
{ id, version_number }: { id: string | undefined; version_number?: number },
{ id, version_number: version_number_prop }: { id: string | undefined; version_number?: number },
select?: (data: IBusterMetric) => TData
) => {
const searchParams = useSearchParams();
const queryVersionNumber = searchParams.get('metric_version_number');
const getAssetPassword = useBusterAssetsContextSelector((x) => x.getAssetPassword);
const setAssetPasswordError = useBusterAssetsContextSelector((x) => x.setAssetPasswordError);
const { password } = getAssetPassword(id!);
const queryClient = useQueryClient();
const options = metricsQueryKeys.metricsGetMetric(id!);
const version_number = useMemo(() => {
return version_number_prop || queryVersionNumber ? parseInt(queryVersionNumber!) : undefined;
}, [version_number_prop, queryVersionNumber]);
const options = useMemo(() => {
return metricsQueryKeys.metricsGetMetric(id!, version_number);
}, [id, version_number]);
const queryFn = useMemoizedFn(async () => {
const result = await getMetric({ id: id!, password });
const result = await getMetric({ id: id!, password, version_number });
const oldMetric = queryClient.getQueryData(options.queryKey);
return upgradeMetricToIMetric(result, oldMetric || null);
});
@ -65,7 +76,7 @@ export const prefetchGetMetric = async (
) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...metricsQueryKeys.metricsGetMetric(params.id),
...metricsQueryKeys.metricsGetMetric(params.id, params.version_number),
queryFn: async () => {
const result = await getMetric_server(params);
return upgradeMetricToIMetric(result, null);
@ -112,33 +123,44 @@ export const prefetchGetMetricsList = async (
return queryClient;
};
/**
* This is a hook that will use the version number from the URL params if it exists.
*/
export const useGetMetricData = ({
id,
version_number
version_number: version_number_prop
}: {
id: string;
version_number?: number;
}) => {
const searchParams = useSearchParams();
const queryVersionNumber = searchParams.get('metric_version_number');
const version_number = useMemo(() => {
return version_number_prop || queryVersionNumber ? parseInt(queryVersionNumber!) : undefined;
}, [version_number_prop, queryVersionNumber]);
const queryFn = useMemoizedFn(() => {
return getMetricData({ id, version_number });
});
return useQuery({
...metricsQueryKeys.metricsGetData(id),
...metricsQueryKeys.metricsGetData(id, version_number),
queryFn,
enabled: !!id
});
};
export const prefetchGetMetricDataClient = async (
{ id }: { id: string },
{ id, version_number }: { id: string; version_number?: number },
queryClient: QueryClient
) => {
const options = metricsQueryKeys.metricsGetData(id);
const options = metricsQueryKeys.metricsGetData(id, version_number);
const existingData = queryClient.getQueryData(options.queryKey);
if (!existingData) {
await queryClient.prefetchQuery({
...options,
queryFn: () => getMetricData({ id })
queryFn: () => getMetricData({ id, version_number })
});
}
};

View File

@ -19,9 +19,9 @@ export const metricsGetList = (filters?: Parameters<typeof listMetrics>[0]) =>
staleTime: 10 * 1000
});
export const metricsGetData = (id: string) =>
export const metricsGetData = (id: string, version_number?: number) =>
queryOptions<IBusterMetricData>({
queryKey: ['metrics', 'data', id] as const,
queryKey: ['metrics', 'data', id, version_number] as const,
staleTime: 3 * 60 * 60 * 1000 // 3 hours,
});

View File

@ -2,7 +2,7 @@ import { MetricController } from '@/controllers/MetricController';
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
export default async function MetricPage(props: { params: Promise<{ metricId: string }> }) {
const params = await props.params;
const [params] = await Promise.all([props.params]);
const { metricId } = params;
return (

View File

@ -4,9 +4,19 @@ import { useGetMetric } from '@/api/buster_rest/metrics';
import { CircleSpinnerLoaderContainer } from '@/components/ui/loaders';
import { MetricViewChart } from '@/controllers/MetricController/MetricViewChart/MetricViewChart';
export default function EmbedMetricsPage({ params }: { params: { metricId: string } }) {
export default function EmbedMetricsPage({
params,
searchParams
}: {
params: { metricId: string };
searchParams: { version_number?: string };
}) {
const { metricId } = params;
const { isFetched } = useGetMetric({ id: metricId });
const { version_number } = searchParams;
const { isFetched, error, ...rest } = useGetMetric({
id: metricId,
version_number: version_number ? parseInt(version_number) : undefined
});
if (!isFetched) {
return <CircleSpinnerLoaderContainer className="min-h-screen" />;

View File

@ -8,8 +8,11 @@ const statusVariants = cva('shadow p-3 rounded', {
variants: {
variant: {
danger: 'bg-danger-foreground text-white',
default: 'bg-background text-foreground'
default: 'bg-background text-foreground border'
}
},
defaultVariants: {
variant: 'default'
}
});
@ -21,7 +24,7 @@ export const StatusCard: React.FC<
onClose?: () => void;
extra?: React.ReactNode;
} & VariantProps<typeof statusVariants>
> = ({ message, title, variant, className, onClose, extra }) => {
> = ({ message, title, variant = 'default', className, onClose, extra }) => {
return (
<div className={cn('flex flex-col gap-1', statusVariants({ variant }), className)}>
{title && <Text variant={'inherit'}>{title}</Text>}

View File

@ -4,7 +4,17 @@ import { useInViewport } from '@/hooks';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
export const useDashboardMetric = ({ metricId }: { metricId: string }) => {
const { data: metric, isFetched: isMetricFetched } = useGetMetric({ id: metricId });
const { data: metric, isFetched: isMetricFetched } = useGetMetric(
{ id: metricId },
({ name, description, time_frame, permission, evaluation_score, evaluation_summary }) => ({
name,
description,
time_frame,
permission,
evaluation_score,
evaluation_summary
})
);
const {
data: metricData,
isFetched: isFetchedMetricData,

View File

@ -8,25 +8,34 @@ import {
import { MetricViewComponents } from './config';
import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import { MetricViewError } from './MetricViewError';
export const MetricController: React.FC<{
metricId: string;
}> = React.memo(({ metricId }) => {
const { isFetched: isMetricFetched } = useGetMetric({ id: metricId });
const { isFetched: isMetricFetched, error: metricError } = useGetMetric({ id: metricId });
const { isFetched: isMetricDataFetched } = useGetMetricData({ id: metricId });
const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView) || 'chart';
const showLoader = !isMetricFetched || !isMetricDataFetched;
const Component =
isMetricFetched && selectedFileView in MetricViewComponents
? MetricViewComponents[selectedFileView as MetricFileView]
: () => <></>;
const Component = React.useMemo(() => {
if (metricError) {
return <MetricViewError error={metricError.message} />;
}
if (isMetricFetched && selectedFileView in MetricViewComponents) {
const Component = MetricViewComponents[selectedFileView as MetricFileView];
return <Component metricId={metricId} />;
}
return null;
}, [isMetricFetched, selectedFileView, metricError, metricId]);
return (
<>
{showLoader && <FileIndeterminateLoader />}
{Component && <Component metricId={metricId} />}
{Component}
</>
);
});

View File

@ -13,7 +13,6 @@ import {
ScatterAxis
} from '@/api/asset_interfaces/metric/charts';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import { useUnmount } from '@/hooks';
export const MetricStylingApp: React.FC<{
metricId: string;
@ -21,15 +20,14 @@ export const MetricStylingApp: React.FC<{
const [segment, setSegment] = useState<MetricStylingAppSegments>(
MetricStylingAppSegments.VISUALIZE
);
const { data: metric } = useGetMetric({ id: metricId });
const { data: chartConfig } = useGetMetric({ id: metricId }, (x) => x.chart_config);
const { data: metricData } = useGetMetricData({ id: metricId });
if (!metric) return null;
if (!chartConfig) return null;
const columnMetadata = metricData?.data_metadata?.column_metadata || [];
const rowCount = metricData?.data_metadata?.row_count || 0;
const chartConfig = metric.chart_config;
const {
selectedChartType,
lineGroupType,

View File

@ -17,7 +17,26 @@ export const MetricViewChart: React.FC<{
cardClassName?: string;
}> = React.memo(
({ metricId, readOnly: readOnlyProp = false, className = '', cardClassName = '' }) => {
const { data: metric } = useGetMetric({ id: metricId });
const { data: metric } = useGetMetric(
{ id: metricId },
({
chart_config,
name,
description,
time_frame,
permission,
evaluation_score,
evaluation_summary
}) => ({
name,
description,
time_frame,
permission,
evaluation_score,
evaluation_summary,
chart_config
})
);
const {
data: metricData,
isFetched: isFetchedMetricData,

View File

@ -0,0 +1,13 @@
import React from 'react';
import { Text, Title } from '@/components/ui/typography';
import { StatusCard } from '@/components/ui/card/StatusCard';
export const MetricViewError: React.FC<{ error: string | undefined }> = ({
error = `The metric you are trying to view has an error. Please contact support if the problem persists.`
}) => {
return (
<div className="mx-6 mt-6">
<StatusCard message={error} title="Metric Error" />
</div>
);
};

View File

@ -0,0 +1 @@
export * from './MetricViewError';

View File

@ -7,7 +7,10 @@ import { useBusterNotifications } from '@/context/BusterNotifications';
import { useGetMetric, useUpdateMetric } from '@/api/buster_rest/metrics';
export const MetricViewFile: React.FC<MetricViewProps> = React.memo(({ metricId }) => {
const { data: metric } = useGetMetric({ id: metricId });
const { data: metric } = useGetMetric({ id: metricId }, ({ file, file_name }) => ({
file,
file_name
}));
const { openSuccessMessage } = useBusterNotifications();
const { mutateAsync: updateMetric } = useUpdateMetric();

View File

@ -21,7 +21,10 @@ export const MetricViewResults: React.FC<MetricViewProps> = React.memo(({ metric
const { runSQL, resetRunSQLData, saveSQL, warnBeforeNavigating, setWarnBeforeNavigating } =
useMetricRunSQL();
const { data: metric } = useGetMetric({ id: metricId });
const { data: metric } = useGetMetric({ id: metricId }, ({ sql, data_source_id }) => ({
sql,
data_source_id
}));
const { data: metricData } = useGetMetricData({ id: metricId });
const [sql, setSQL] = React.useState(metric?.sql || '');

View File

@ -261,7 +261,7 @@ const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => {
};
const useStatusSelectMenu = ({ metricId }: { metricId: string }) => {
const { data: metric } = useGetMetric({ id: metricId }, (x) => x);
const { data: metricStatus } = useGetMetric({ id: metricId }, (x) => x.status);
const { mutateAsync: updateMetric } = useUpdateMetric();
const onChangeStatus = useMemoizedFn(async (status: VerificationStatus) => {
@ -270,7 +270,7 @@ const useStatusSelectMenu = ({ metricId }: { metricId: string }) => {
const dropdownProps = useStatusDropdownContent({
isAdmin: true,
selectedStatus: metric?.status || VerificationStatus.NOT_REQUESTED,
selectedStatus: metricStatus || VerificationStatus.NOT_REQUESTED,
onChangeStatus
});
@ -282,7 +282,7 @@ const useStatusSelectMenu = ({ metricId }: { metricId: string }) => {
() => ({
label: 'Status',
value: 'status',
icon: <StatusBadgeIndicator status={metric?.status || VerificationStatus.NOT_REQUESTED} />,
icon: <StatusBadgeIndicator status={metricStatus || VerificationStatus.NOT_REQUESTED} />,
items: [<React.Fragment key="status-sub-menu">{statusSubMenu}</React.Fragment>]
}),
[statusSubMenu]