mirror of https://github.com/buster-so/buster.git
error for metric
This commit is contained in:
parent
a6e12cb06b
commit
0ebcc6ffab
|
@ -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 { useDebounceFn, useMemoizedFn, useMount, useUnmount } from '@/hooks';
|
import { useDebounceFn, useMemoizedFn } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
deleteMetrics,
|
deleteMetrics,
|
||||||
duplicateMetric,
|
duplicateMetric,
|
||||||
|
@ -26,21 +26,32 @@ import {
|
||||||
useAddAssetToCollection,
|
useAddAssetToCollection,
|
||||||
useRemoveAssetFromCollection
|
useRemoveAssetFromCollection
|
||||||
} from '../collections/queryRequests';
|
} 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>(
|
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
|
select?: (data: IBusterMetric) => TData
|
||||||
) => {
|
) => {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const queryVersionNumber = searchParams.get('metric_version_number');
|
||||||
const getAssetPassword = useBusterAssetsContextSelector((x) => x.getAssetPassword);
|
const getAssetPassword = useBusterAssetsContextSelector((x) => x.getAssetPassword);
|
||||||
const setAssetPasswordError = useBusterAssetsContextSelector((x) => x.setAssetPasswordError);
|
const setAssetPasswordError = useBusterAssetsContextSelector((x) => x.setAssetPasswordError);
|
||||||
const { password } = getAssetPassword(id!);
|
const { password } = getAssetPassword(id!);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
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 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);
|
const oldMetric = queryClient.getQueryData(options.queryKey);
|
||||||
return upgradeMetricToIMetric(result, oldMetric || null);
|
return upgradeMetricToIMetric(result, oldMetric || null);
|
||||||
});
|
});
|
||||||
|
@ -65,7 +76,7 @@ export const prefetchGetMetric = async (
|
||||||
) => {
|
) => {
|
||||||
const queryClient = queryClientProp || new QueryClient();
|
const queryClient = queryClientProp || new QueryClient();
|
||||||
await queryClient.prefetchQuery({
|
await queryClient.prefetchQuery({
|
||||||
...metricsQueryKeys.metricsGetMetric(params.id),
|
...metricsQueryKeys.metricsGetMetric(params.id, params.version_number),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const result = await getMetric_server(params);
|
const result = await getMetric_server(params);
|
||||||
return upgradeMetricToIMetric(result, null);
|
return upgradeMetricToIMetric(result, null);
|
||||||
|
@ -112,33 +123,44 @@ export const prefetchGetMetricsList = async (
|
||||||
return queryClient;
|
return queryClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a hook that will use the version number from the URL params if it exists.
|
||||||
|
*/
|
||||||
export const useGetMetricData = ({
|
export const useGetMetricData = ({
|
||||||
id,
|
id,
|
||||||
version_number
|
version_number: version_number_prop
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
version_number?: number;
|
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(() => {
|
const queryFn = useMemoizedFn(() => {
|
||||||
return getMetricData({ id, version_number });
|
return getMetricData({ id, version_number });
|
||||||
});
|
});
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
...metricsQueryKeys.metricsGetData(id),
|
...metricsQueryKeys.metricsGetData(id, version_number),
|
||||||
queryFn,
|
queryFn,
|
||||||
enabled: !!id
|
enabled: !!id
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prefetchGetMetricDataClient = async (
|
export const prefetchGetMetricDataClient = async (
|
||||||
{ id }: { id: string },
|
{ id, version_number }: { id: string; version_number?: number },
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
) => {
|
) => {
|
||||||
const options = metricsQueryKeys.metricsGetData(id);
|
const options = metricsQueryKeys.metricsGetData(id, version_number);
|
||||||
const existingData = queryClient.getQueryData(options.queryKey);
|
const existingData = queryClient.getQueryData(options.queryKey);
|
||||||
if (!existingData) {
|
if (!existingData) {
|
||||||
await queryClient.prefetchQuery({
|
await queryClient.prefetchQuery({
|
||||||
...options,
|
...options,
|
||||||
queryFn: () => getMetricData({ id })
|
queryFn: () => getMetricData({ id, version_number })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,9 +19,9 @@ export const metricsGetList = (filters?: Parameters<typeof listMetrics>[0]) =>
|
||||||
staleTime: 10 * 1000
|
staleTime: 10 * 1000
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metricsGetData = (id: string) =>
|
export const metricsGetData = (id: string, version_number?: number) =>
|
||||||
queryOptions<IBusterMetricData>({
|
queryOptions<IBusterMetricData>({
|
||||||
queryKey: ['metrics', 'data', id] as const,
|
queryKey: ['metrics', 'data', id, version_number] as const,
|
||||||
staleTime: 3 * 60 * 60 * 1000 // 3 hours,
|
staleTime: 3 * 60 * 60 * 1000 // 3 hours,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { MetricController } from '@/controllers/MetricController';
|
||||||
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
|
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
|
||||||
|
|
||||||
export default async function MetricPage(props: { params: Promise<{ metricId: string }> }) {
|
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;
|
const { metricId } = params;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,9 +4,19 @@ import { useGetMetric } from '@/api/buster_rest/metrics';
|
||||||
import { CircleSpinnerLoaderContainer } from '@/components/ui/loaders';
|
import { CircleSpinnerLoaderContainer } from '@/components/ui/loaders';
|
||||||
import { MetricViewChart } from '@/controllers/MetricController/MetricViewChart/MetricViewChart';
|
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 { 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) {
|
if (!isFetched) {
|
||||||
return <CircleSpinnerLoaderContainer className="min-h-screen" />;
|
return <CircleSpinnerLoaderContainer className="min-h-screen" />;
|
||||||
|
|
|
@ -8,8 +8,11 @@ const statusVariants = cva('shadow p-3 rounded', {
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
danger: 'bg-danger-foreground text-white',
|
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;
|
onClose?: () => void;
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
} & VariantProps<typeof statusVariants>
|
} & VariantProps<typeof statusVariants>
|
||||||
> = ({ message, title, variant, className, onClose, extra }) => {
|
> = ({ message, title, variant = 'default', className, onClose, extra }) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex flex-col gap-1', statusVariants({ variant }), className)}>
|
<div className={cn('flex flex-col gap-1', statusVariants({ variant }), className)}>
|
||||||
{title && <Text variant={'inherit'}>{title}</Text>}
|
{title && <Text variant={'inherit'}>{title}</Text>}
|
||||||
|
|
|
@ -4,7 +4,17 @@ import { useInViewport } from '@/hooks';
|
||||||
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
||||||
|
|
||||||
export const useDashboardMetric = ({ metricId }: { metricId: string }) => {
|
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 {
|
const {
|
||||||
data: metricData,
|
data: metricData,
|
||||||
isFetched: isFetchedMetricData,
|
isFetched: isFetchedMetricData,
|
||||||
|
|
|
@ -8,25 +8,34 @@ import {
|
||||||
import { MetricViewComponents } from './config';
|
import { MetricViewComponents } from './config';
|
||||||
import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader';
|
import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader';
|
||||||
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
||||||
|
import { MetricViewError } from './MetricViewError';
|
||||||
|
|
||||||
export const MetricController: React.FC<{
|
export const MetricController: React.FC<{
|
||||||
metricId: string;
|
metricId: string;
|
||||||
}> = React.memo(({ metricId }) => {
|
}> = React.memo(({ metricId }) => {
|
||||||
const { isFetched: isMetricFetched } = useGetMetric({ id: metricId });
|
const { isFetched: isMetricFetched, error: metricError } = useGetMetric({ id: metricId });
|
||||||
const { isFetched: isMetricDataFetched } = useGetMetricData({ id: metricId });
|
const { isFetched: isMetricDataFetched } = useGetMetricData({ id: metricId });
|
||||||
const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView) || 'chart';
|
const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView) || 'chart';
|
||||||
|
|
||||||
const showLoader = !isMetricFetched || !isMetricDataFetched;
|
const showLoader = !isMetricFetched || !isMetricDataFetched;
|
||||||
|
|
||||||
const Component =
|
const Component = React.useMemo(() => {
|
||||||
isMetricFetched && selectedFileView in MetricViewComponents
|
if (metricError) {
|
||||||
? MetricViewComponents[selectedFileView as MetricFileView]
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{showLoader && <FileIndeterminateLoader />}
|
{showLoader && <FileIndeterminateLoader />}
|
||||||
{Component && <Component metricId={metricId} />}
|
{Component}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
ScatterAxis
|
ScatterAxis
|
||||||
} from '@/api/asset_interfaces/metric/charts';
|
} from '@/api/asset_interfaces/metric/charts';
|
||||||
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
||||||
import { useUnmount } from '@/hooks';
|
|
||||||
|
|
||||||
export const MetricStylingApp: React.FC<{
|
export const MetricStylingApp: React.FC<{
|
||||||
metricId: string;
|
metricId: string;
|
||||||
|
@ -21,15 +20,14 @@ export const MetricStylingApp: React.FC<{
|
||||||
const [segment, setSegment] = useState<MetricStylingAppSegments>(
|
const [segment, setSegment] = useState<MetricStylingAppSegments>(
|
||||||
MetricStylingAppSegments.VISUALIZE
|
MetricStylingAppSegments.VISUALIZE
|
||||||
);
|
);
|
||||||
const { data: metric } = useGetMetric({ id: metricId });
|
const { data: chartConfig } = useGetMetric({ id: metricId }, (x) => x.chart_config);
|
||||||
const { data: metricData } = useGetMetricData({ id: metricId });
|
const { data: metricData } = useGetMetricData({ id: metricId });
|
||||||
|
|
||||||
if (!metric) return null;
|
if (!chartConfig) return null;
|
||||||
|
|
||||||
const columnMetadata = metricData?.data_metadata?.column_metadata || [];
|
const columnMetadata = metricData?.data_metadata?.column_metadata || [];
|
||||||
const rowCount = metricData?.data_metadata?.row_count || 0;
|
const rowCount = metricData?.data_metadata?.row_count || 0;
|
||||||
|
|
||||||
const chartConfig = metric.chart_config;
|
|
||||||
const {
|
const {
|
||||||
selectedChartType,
|
selectedChartType,
|
||||||
lineGroupType,
|
lineGroupType,
|
||||||
|
|
|
@ -17,7 +17,26 @@ export const MetricViewChart: React.FC<{
|
||||||
cardClassName?: string;
|
cardClassName?: string;
|
||||||
}> = React.memo(
|
}> = React.memo(
|
||||||
({ metricId, readOnly: readOnlyProp = false, className = '', cardClassName = '' }) => {
|
({ 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 {
|
const {
|
||||||
data: metricData,
|
data: metricData,
|
||||||
isFetched: isFetchedMetricData,
|
isFetched: isFetchedMetricData,
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './MetricViewError';
|
|
@ -7,7 +7,10 @@ import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
import { useGetMetric, useUpdateMetric } from '@/api/buster_rest/metrics';
|
import { useGetMetric, useUpdateMetric } from '@/api/buster_rest/metrics';
|
||||||
|
|
||||||
export const MetricViewFile: React.FC<MetricViewProps> = React.memo(({ metricId }) => {
|
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 { openSuccessMessage } = useBusterNotifications();
|
||||||
const { mutateAsync: updateMetric } = useUpdateMetric();
|
const { mutateAsync: updateMetric } = useUpdateMetric();
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,10 @@ export const MetricViewResults: React.FC<MetricViewProps> = React.memo(({ metric
|
||||||
const { runSQL, resetRunSQLData, saveSQL, warnBeforeNavigating, setWarnBeforeNavigating } =
|
const { runSQL, resetRunSQLData, saveSQL, warnBeforeNavigating, setWarnBeforeNavigating } =
|
||||||
useMetricRunSQL();
|
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 { data: metricData } = useGetMetricData({ id: metricId });
|
||||||
|
|
||||||
const [sql, setSQL] = React.useState(metric?.sql || '');
|
const [sql, setSQL] = React.useState(metric?.sql || '');
|
||||||
|
|
|
@ -261,7 +261,7 @@ const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStatusSelectMenu = ({ 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 { mutateAsync: updateMetric } = useUpdateMetric();
|
||||||
|
|
||||||
const onChangeStatus = useMemoizedFn(async (status: VerificationStatus) => {
|
const onChangeStatus = useMemoizedFn(async (status: VerificationStatus) => {
|
||||||
|
@ -270,7 +270,7 @@ const useStatusSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||||
|
|
||||||
const dropdownProps = useStatusDropdownContent({
|
const dropdownProps = useStatusDropdownContent({
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
selectedStatus: metric?.status || VerificationStatus.NOT_REQUESTED,
|
selectedStatus: metricStatus || VerificationStatus.NOT_REQUESTED,
|
||||||
onChangeStatus
|
onChangeStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ const useStatusSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||||
() => ({
|
() => ({
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
value: '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>]
|
items: [<React.Fragment key="status-sub-menu">{statusSubMenu}</React.Fragment>]
|
||||||
}),
|
}),
|
||||||
[statusSubMenu]
|
[statusSubMenu]
|
||||||
|
|
Loading…
Reference in New Issue