versions for dashboard

This commit is contained in:
Nate Kelley 2025-04-16 23:47:14 -06:00
parent 99b6436418
commit ba16fcac4a
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
21 changed files with 320 additions and 225 deletions

View File

@ -0,0 +1,183 @@
import { BusterDashboardResponse } from '@/api/asset_interfaces/dashboard';
import { queryKeys } from '@/api/query_keys';
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { useOriginalDashboardStore } from '@/context/Dashboards';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { upgradeMetricToIMetric } from '@/lib/metrics/upgradeToIMetric';
import { Query, useQueryClient } from '@tanstack/react-query';
import { prefetchGetMetricDataClient } from '../metrics/queryRequests';
import { dashboardsGetDashboard } from './requests';
import { useParams, useSearchParams } from 'next/navigation';
import { useMemo } from 'react';
import { RustApiError } from '../errors';
import last from 'lodash/last';
export const useEnsureDashboardConfig = (prefetchData: boolean = true) => {
const queryClient = useQueryClient();
const prefetchDashboard = useGetDashboardAndInitializeMetrics(prefetchData);
const { openErrorMessage } = useBusterNotifications();
const getLatestDashboardVersion = useGetLatestDashboardVersionNumber();
const method = useMemoizedFn(async (dashboardId: string) => {
const latestVersion = getLatestDashboardVersion(dashboardId);
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, latestVersion);
let dashboardResponse = queryClient.getQueryData(options.queryKey);
if (!dashboardResponse) {
const res = await prefetchDashboard(dashboardId, latestVersion || undefined).catch((e) => {
openErrorMessage('Failed to save metrics to dashboard. Dashboard not found');
return null;
});
if (res) {
queryClient.setQueryData(options.queryKey, res);
dashboardResponse = res;
}
}
return dashboardResponse;
});
return method;
};
export const useGetDashboardAndInitializeMetrics = (prefetchData: boolean = true) => {
const queryClient = useQueryClient();
const setOriginalDashboards = useOriginalDashboardStore((x) => x.setOriginalDashboard);
const getAssetPassword = useBusterAssetsContextSelector((state) => state.getAssetPassword);
const initializeMetrics = useMemoizedFn((metrics: BusterDashboardResponse['metrics']) => {
for (const metric of Object.values(metrics)) {
const prevMetric = queryClient.getQueryData(
queryKeys.metricsGetMetric(metric.id, metric.version_number).queryKey
);
const upgradedMetric = upgradeMetricToIMetric(metric, prevMetric);
queryClient.setQueryData(
queryKeys.metricsGetMetric(metric.id, metric.version_number).queryKey,
upgradedMetric
);
if (prefetchData) {
prefetchGetMetricDataClient(
{ id: metric.id, version_number: metric.version_number },
queryClient
);
}
}
});
return useMemoizedFn(async (id: string, version_number: number | null | undefined) => {
const { password } = getAssetPassword?.(id) || {};
return dashboardsGetDashboard({
id: id!,
password,
version_number: version_number || undefined
}).then((data) => {
initializeMetrics(data.metrics);
setOriginalDashboards(data.dashboard);
if (!version_number && data.dashboard.version_number) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(id, data.dashboard.version_number).queryKey,
data
);
}
return data;
});
});
};
export const useGetDashboardVersionNumber = (props?: {
versionNumber?: number | null; //if null it will not use a params from the query params
}) => {
const { versionNumber: versionNumberProp } = props || {};
const { dashboardId: dashboardIdPathParam } = useParams() as {
dashboardId: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('dashboard_version_number');
const versionNumberFromParams = dashboardIdPathParam ? versionNumberQueryParam : undefined;
const paramVersionNumber = useMemo(() => {
return (
versionNumberProp ??
(versionNumberFromParams ? parseInt(versionNumberFromParams!) : undefined)
);
}, [versionNumberProp, versionNumberFromParams]);
const latestVersionNumber = useGetLatestDashboardVersion({ dashboardId: dashboardIdPathParam! });
const selectedVersionNumber: number | null = useMemo(() => {
if (versionNumberProp === null) return null;
return paramVersionNumber || latestVersionNumber || 0;
}, [paramVersionNumber, latestVersionNumber]);
return useMemo(() => {
return { selectedVersionNumber, paramVersionNumber, latestVersionNumber };
}, [selectedVersionNumber, selectedVersionNumber, latestVersionNumber]);
};
type PredicateType = (query: Query<BusterDashboardResponse, RustApiError>) => boolean;
const filterMetricPredicate = <PredicateType>((query) => {
const lastKey = last(query.queryKey);
return (
typeof lastKey === 'number' &&
!!lastKey &&
query.state.data !== undefined &&
typeof query.state.data === 'object' &&
query.state.data !== null &&
'versions' in query.state.data
);
});
const getLatestVersionNumber = (
queries: [readonly unknown[], BusterDashboardResponse | undefined][]
) => {
let maxVersion = -Infinity;
// Single pass: filter and find max version
for (const [queryKey, data] of queries) {
if (data && typeof data === 'object' && 'versions' in data) {
const lastVersion = last(data.versions);
const version = Number(lastVersion?.version_number);
maxVersion = Math.max(maxVersion, version, data.dashboard.version_number);
}
}
return maxVersion === -Infinity ? null : maxVersion;
};
const useGetLatestDashboardVersion = ({ dashboardId }: { dashboardId: string }) => {
const queryClient = useQueryClient();
const memoizedKey = useMemo(() => {
return dashboardQueryKeys.dashboardGetDashboard(dashboardId, null).queryKey.slice(0, -1);
}, [dashboardId]);
const queries = queryClient.getQueriesData<BusterDashboardResponse, any>({
queryKey: memoizedKey,
predicate: filterMetricPredicate
});
const latestVersion = useMemo(() => {
return getLatestVersionNumber(queries);
}, [queries.length]);
return latestVersion;
};
//This is a helper function that returns the latest version number for a metric
export const useGetLatestDashboardVersionNumber = () => {
const queryClient = useQueryClient();
const method = useMemoizedFn((dashboardId: string) => {
const queries = queryClient.getQueriesData<BusterDashboardResponse, any>({
queryKey: dashboardQueryKeys.dashboardGetDashboard(dashboardId, null).queryKey.slice(0, -1),
predicate: filterMetricPredicate
});
return getLatestVersionNumber(queries);
});
return method;
};

View File

@ -1,109 +0,0 @@
import { BusterDashboardResponse } from '@/api/asset_interfaces/dashboard';
import { IBusterMetric } from '@/api/asset_interfaces/metric';
import { queryKeys } from '@/api/query_keys';
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
import { metricsQueryKeys } from '@/api/query_keys/metric';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { useOriginalDashboardStore } from '@/context/Dashboards';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { upgradeMetricToIMetric } from '@/lib/metrics/upgradeToIMetric';
import { useQueryClient } from '@tanstack/react-query';
import { prefetchGetMetricDataClient } from '../metrics/queryRequests';
import { dashboardsGetDashboard } from './requests';
import { useParams, useSearchParams } from 'next/navigation';
import { useMemo } from 'react';
export const useEnsureDashboardConfig = (prefetchData: boolean = true) => {
const queryClient = useQueryClient();
const versionNumber = useGetDashboardVersionNumber();
const prefetchDashboard = useGetDashboardAndInitializeMetrics(prefetchData);
const { openErrorMessage } = useBusterNotifications();
const method = useMemoizedFn(async (dashboardId: string) => {
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, versionNumber);
let dashboardResponse = queryClient.getQueryData(options.queryKey);
if (!dashboardResponse) {
const res = await prefetchDashboard(dashboardId).catch((e) => {
openErrorMessage('Failed to save metrics to dashboard. Dashboard not found');
return null;
});
if (res) {
queryClient.setQueryData(options.queryKey, res);
dashboardResponse = res;
}
}
return dashboardResponse;
});
return method;
};
export const useGetDashboardAndInitializeMetrics = (prefetchData: boolean = true) => {
const queryClient = useQueryClient();
const setOriginalDashboards = useOriginalDashboardStore((x) => x.setOriginalDashboard);
const getAssetPassword = useBusterAssetsContextSelector((state) => state.getAssetPassword);
const initializeMetrics = useMemoizedFn((metrics: BusterDashboardResponse['metrics']) => {
for (const metric of Object.values(metrics)) {
const prevMetric = queryClient.getQueryData(
queryKeys.metricsGetMetric(metric.id, metric.version_number).queryKey
);
const upgradedMetric = upgradeMetricToIMetric(metric, prevMetric);
queryClient.setQueryData(
queryKeys.metricsGetMetric(metric.id, metric.version_number).queryKey,
upgradedMetric
);
if (prefetchData) {
prefetchGetMetricDataClient(
{ id: metric.id, version_number: metric.version_number },
queryClient
);
}
}
});
return useMemoizedFn(async (id: string, version_number?: number) => {
const { password } = getAssetPassword?.(id) || {};
return dashboardsGetDashboard({ id: id!, password, version_number }).then((data) => {
initializeMetrics(data.metrics);
setOriginalDashboards(data.dashboard);
if (!version_number && data.dashboard.version_number) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(id, data.dashboard.version_number).queryKey,
data
);
}
return data;
});
});
};
export const useGetDashboardVersionNumber = (props?: {
versionNumber?: number | null; //if null it will not use a params from the query params
}) => {
const { versionNumber: versionNumberProp } = props || {};
const { versionNumber: versionNumberPathParam, dashboardId: dashboardIdPathParam } =
useParams() as {
versionNumber: string | undefined;
dashboardId: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('dashboard_version_number');
const versionNumberFromParams = dashboardIdPathParam
? versionNumberQueryParam || versionNumberPathParam
: undefined;
const versionNumber = useMemo(() => {
if (versionNumberProp === null) return undefined;
return (
versionNumberProp ??
(versionNumberFromParams ? parseInt(versionNumberFromParams!) : undefined)
);
}, [versionNumberProp, versionNumberFromParams]);
return versionNumber;
};

View File

@ -32,8 +32,9 @@ import {
useGetDashboardAndInitializeMetrics,
useGetDashboardVersionNumber,
useEnsureDashboardConfig
} from './queryHelpers';
} from './dashboardQueryHelpers';
import { useGetLatestMetricVersionNumber } from '../metrics';
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
export const useGetDashboard = <TData = BusterDashboardResponse>(
{
@ -46,14 +47,29 @@ export const useGetDashboard = <TData = BusterDashboardResponse>(
>
) => {
const queryFn = useGetDashboardAndInitializeMetrics();
const versionNumber = useGetDashboardVersionNumber({ versionNumber: versionNumberProp });
const setAssetPasswordError = useBusterAssetsContextSelector((x) => x.setAssetPasswordError);
const { selectedVersionNumber, latestVersionNumber, paramVersionNumber } =
useGetDashboardVersionNumber({ versionNumber: versionNumberProp });
const { isFetched: isFetchedInitial, isError: isErrorInitial } = useQuery({
...dashboardQueryKeys.dashboardGetDashboard(id!, null),
queryFn: () => queryFn(id!, paramVersionNumber),
enabled: false, //we made this false because we want to be explicit about the fact that we fetch the dashboard server side
retry(failureCount, error) {
if (error?.message !== undefined) {
setAssetPasswordError(id!, error.message || 'An error occurred');
}
return false;
},
select: undefined,
...params
});
return useQuery({
...dashboardQueryKeys.dashboardGetDashboard(id!, versionNumber),
queryFn: () => queryFn(id!, versionNumber),
enabled: false, //we made this false because we want to be explicit about the fact that we fetch the dashboard server side
select: params?.select,
...params
...dashboardQueryKeys.dashboardGetDashboard(id!, selectedVersionNumber),
queryFn: () => queryFn(id!, selectedVersionNumber),
enabled: !!latestVersionNumber && isFetchedInitial && !isErrorInitial,
select: params?.select
});
};
@ -72,7 +88,6 @@ export const useSaveDashboard = (params?: { updateOnSave?: boolean }) => {
const updateOnSave = params?.updateOnSave || false;
const queryClient = useQueryClient();
const setOriginalDashboard = useOriginalDashboardStore((x) => x.setOriginalDashboard);
const versionNumber = useGetDashboardVersionNumber();
return useMutation({
mutationFn: dashboardsUpdateDashboard,
@ -89,13 +104,6 @@ export const useSaveDashboard = (params?: { updateOnSave?: boolean }) => {
.queryKey,
data
);
//We need to update BOTH the versioned and the non-versioned metric for version updates to keep the latest up to date
if (variables.update_version || variables.restore_to_version) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, undefined).queryKey,
data
);
}
setOriginalDashboard(data.dashboard);
}
}
@ -110,7 +118,7 @@ export const useUpdateDashboard = (params?: {
const { updateOnSave = false, updateVersion = false, saveToServer = false } = params || {};
const queryClient = useQueryClient();
const { mutateAsync: saveDashboard } = useSaveDashboard({ updateOnSave });
const versionNumber = useGetDashboardVersionNumber();
const { latestVersionNumber } = useGetDashboardVersionNumber();
const getOriginalDashboard = useOriginalDashboardStore((x) => x.getOriginalDashboard);
const mutationFn = useMemoizedFn(
@ -133,8 +141,7 @@ export const useUpdateDashboard = (params?: {
});
const queryKey = dashboardQueryKeys.dashboardGetDashboard(
variables.id,
versionNumber
// updatedDashboard.version_number
latestVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
@ -152,7 +159,7 @@ export const useUpdateDashboardConfig = () => {
updateVersion: false
});
const queryClient = useQueryClient();
const versionNumber = useGetDashboardVersionNumber();
const { latestVersionNumber } = useGetDashboardVersionNumber();
const method = useMemoizedFn(
async ({
@ -161,7 +168,7 @@ export const useUpdateDashboardConfig = () => {
}: Partial<BusterDashboard['config']> & {
dashboardId: string;
}) => {
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, versionNumber);
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, latestVersionNumber);
const previousDashboard = queryClient.getQueryData(options.queryKey);
const previousConfig = previousDashboard?.dashboard?.config;
if (previousConfig) {
@ -304,13 +311,13 @@ export const useRemoveDashboardFromCollection = () => {
export const useShareDashboard = () => {
const queryClient = useQueryClient();
const versionNumber = useGetDashboardVersionNumber();
const { latestVersionNumber } = useGetDashboardVersionNumber();
return useMutation({
mutationFn: shareDashboard,
onMutate: (variables) => {
const queryKey = dashboardQueryKeys.dashboardGetDashboard(
variables.id,
versionNumber
latestVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
return create(previousData!, (draft) => {
@ -323,7 +330,8 @@ export const useShareDashboard = () => {
},
onSuccess: (data) => {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, versionNumber).queryKey,
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
data
);
}
@ -332,13 +340,13 @@ export const useShareDashboard = () => {
export const useUnshareDashboard = () => {
const queryClient = useQueryClient();
const versionNumber = useGetDashboardVersionNumber();
const { latestVersionNumber } = useGetDashboardVersionNumber();
return useMutation({
mutationFn: unshareDashboard,
onMutate: (variables) => {
const queryKey = dashboardQueryKeys.dashboardGetDashboard(
variables.id,
versionNumber
latestVersionNumber
).queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
return create(previousData!, (draft) => {
@ -349,7 +357,8 @@ export const useUnshareDashboard = () => {
},
onSuccess: (data) => {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, versionNumber).queryKey,
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
data
);
}
@ -358,11 +367,11 @@ export const useUnshareDashboard = () => {
export const useUpdateDashboardShare = () => {
const queryClient = useQueryClient();
const versionNumber = useGetDashboardVersionNumber();
const { latestVersionNumber } = useGetDashboardVersionNumber();
return useMutation({
mutationFn: updateDashboardShare,
onMutate: ({ id, params }) => {
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, versionNumber).queryKey;
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, latestVersionNumber).queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
return create(previousData!, (draft) => {
draft.individual_permissions =
@ -429,10 +438,6 @@ export const useAddAndRemoveMetricsFromDashboard = () => {
mutationFn: addAndRemoveMetrics,
onSuccess: (data, variables) => {
if (data) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, undefined).queryKey,
data
);
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
@ -486,10 +491,6 @@ export const useAddMetricsToDashboard = () => {
},
onSuccess: (data) => {
if (data) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, undefined).queryKey,
data
);
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
@ -554,10 +555,7 @@ export const useRemoveMetricsFromDashboard = () => {
dashboardResponse.dashboard.id,
dashboardResponse.dashboard.version_number
);
const nonVersionedOptions = dashboardQueryKeys.dashboardGetDashboard(
dashboardResponse.dashboard.id,
undefined
);
const newConfig = removeMetricFromDashboardConfig(
metricIds,
dashboardResponse.dashboard.config
@ -568,11 +566,6 @@ export const useRemoveMetricsFromDashboard = () => {
draft.dashboard.config = newConfig;
});
});
queryClient.setQueryData(nonVersionedOptions.queryKey, (currentDashboard) => {
return create(currentDashboard!, (draft) => {
draft.dashboard.config = newConfig;
});
});
const data = await dashboardsUpdateDashboard({
id: dashboardId,
@ -580,7 +573,10 @@ export const useRemoveMetricsFromDashboard = () => {
});
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, undefined).queryKey,
dashboardQueryKeys.dashboardGetDashboard(
data.dashboard.id,
data.dashboard.version_number
).queryKey,
data
);
queryClient.setQueryData(

View File

@ -9,7 +9,7 @@ export const prefetchGetDashboard = async (
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...dashboardQueryKeys.dashboardGetDashboard(params.id, params.version_number),
...dashboardQueryKeys.dashboardGetDashboard(params.id, params.version_number || null),
queryFn: async () => {
return await getDashboard_server(params);
}

View File

@ -4,7 +4,6 @@ import { Query, useQueryClient } from '@tanstack/react-query';
import { IBusterMetric } from '@/api/asset_interfaces/metric';
import { metricsQueryKeys } from '@/api/query_keys/metric';
import last from 'lodash/last';
import { INFINITY } from 'chart.js/helpers';
import { useMemoizedFn } from '@/hooks';
import { RustApiError } from '../errors';
@ -48,6 +47,22 @@ const filterMetricPredicate = <PredicateType>((query) => {
);
});
const getLatestVersionNumber = (queries: [readonly unknown[], IBusterMetric | undefined][]) => {
let latestVersion = -Infinity;
for (const [queryKey, data] of queries) {
if (data && typeof data === 'object' && 'versions' in data) {
const lastVersion = last(data.versions);
const version = Number(lastVersion?.version_number);
if (!isNaN(version)) {
latestVersion = Math.max(latestVersion, version, data.version_number);
}
}
}
return latestVersion;
};
const useGetLatestMetricVersion = ({ metricId }: { metricId: string }) => {
const queryClient = useQueryClient();
@ -61,18 +76,7 @@ const useGetLatestMetricVersion = ({ metricId }: { metricId: string }) => {
});
const latestVersion = useMemo(() => {
let maxVersion = -Infinity;
// Single pass: filter and find max version
for (const [queryKey, data] of queries) {
if (data && typeof data === 'object' && 'versions' in data) {
const lastVersion = last(data.versions);
const version = Number(lastVersion?.version_number);
maxVersion = Math.max(maxVersion, version, data.version_number);
}
}
return maxVersion === -Infinity ? null : maxVersion;
return getLatestVersionNumber(queries);
}, [queries.length]);
return latestVersion;
@ -88,19 +92,7 @@ export const useGetLatestMetricVersionNumber = () => {
predicate: filterMetricPredicate
});
let latestVersion = -INFINITY;
for (const [queryKey, data] of queries) {
if (data && typeof data === 'object' && 'versions' in data) {
const lastVersion = last(data.versions);
const version = Number(lastVersion?.version_number);
if (!isNaN(version)) {
latestVersion = Math.max(latestVersion, version, data.version_number);
}
}
}
return latestVersion;
return getLatestVersionNumber(queries);
});
return method;

View File

@ -13,7 +13,7 @@ export const prefetchGetMetric = async (
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...metricsQueryKeys.metricsGetMetric(params.id, params.version_number),
...metricsQueryKeys.metricsGetMetric(params.id, params.version_number || null),
queryFn: async () => {
const result = await getMetric_server(params);
return upgradeMetricToIMetric(result, null);

View File

@ -27,7 +27,7 @@ export const useSaveMetric = (params?: { updateOnSave?: boolean }) => {
const updateOnSave = params?.updateOnSave || false;
const queryClient = useQueryClient();
const setOriginalMetric = useOriginalMetricStore((x) => x.setOriginalMetric);
const { latestVersionNumber, selectedVersionNumber } = useGetMetricVersionNumber();
const { latestVersionNumber } = useGetMetricVersionNumber();
return useMutation({
mutationFn: updateMetric,
@ -37,7 +37,7 @@ export const useSaveMetric = (params?: { updateOnSave?: boolean }) => {
//set the current metric to the previous it is being restored to
if (isRestoringVersion) {
const oldMetric = queryClient.getQueryData(
metricsQueryKeys.metricsGetMetric(id, selectedVersionNumber).queryKey
metricsQueryKeys.metricsGetMetric(id, latestVersionNumber).queryKey
);
const newMetric = queryClient.getQueryData(
metricsQueryKeys.metricsGetMetric(id, restore_to_version).queryKey
@ -60,7 +60,7 @@ export const useSaveMetric = (params?: { updateOnSave?: boolean }) => {
if (isUpdatingVersion) {
const metric = queryClient.getQueryData(
metricsQueryKeys.metricsGetMetric(id, selectedVersionNumber).queryKey
metricsQueryKeys.metricsGetMetric(id, latestVersionNumber).queryKey
);
if (!metric) return;
const metricVersionNumber = metric?.version_number;

View File

@ -15,9 +15,9 @@ const dashboardGetList = (
initialDataUpdatedAt: 0
});
const dashboardGetDashboard = (dashboardId: string, version_number: number | undefined) =>
const dashboardGetDashboard = (dashboardId: string, version_number: number | null) =>
queryOptions<BusterDashboardResponse>({
queryKey: ['dashboard', 'get', dashboardId, version_number || 'latest'] as const,
queryKey: ['dashboard', 'get', dashboardId, version_number || 'INITIAL'] as const,
staleTime: 10 * 1000
});

View File

@ -82,12 +82,12 @@ const adminTools: ISidebarGroup = {
route: BusterRoutes.APP_LOGS,
id: BusterRoutes.APP_LOGS
},
{
label: 'Terms & Definitions',
icon: <BookOpen4 />,
route: BusterRoutes.APP_TERMS,
id: BusterRoutes.APP_TERMS
},
// {
// label: 'Terms & Definitions',
// icon: <BookOpen4 />,
// route: BusterRoutes.APP_TERMS,
// id: BusterRoutes.APP_TERMS
// },
{
label: 'Datasets',
icon: <Table />,

View File

@ -11,7 +11,10 @@ export const PopupContainer: React.FC<{
<AnimatePresence mode="wait">
{show && (
<motion.div
className={cn('absolute bottom-10 z-50 flex w-full justify-center', className)}
className={cn(
'absolute right-0 bottom-10 left-0 z-50 flex w-full justify-center',
className
)}
initial={{ opacity: 0, y: -3 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -3 }}

View File

@ -30,7 +30,7 @@ const useBusterAssets = () => {
});
} else if (type === 'dashboard') {
await queryClient.invalidateQueries({
queryKey: queryKeys.dashboardGetDashboard(assetId, undefined).queryKey
queryKey: queryKeys.dashboardGetDashboard(assetId, null).queryKey
});
} else if (type === 'collection') {
await queryClient.invalidateQueries({

View File

@ -5,7 +5,7 @@ import { useQueryClient } from '@tanstack/react-query';
export const useGetDashboardMemoized = () => {
const queryClient = useQueryClient();
const getDashboardMemoized = useMemoizedFn((dashboardId: string, versionNumber?: number) => {
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, versionNumber);
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, versionNumber || null);
const data = queryClient.getQueryData(options.queryKey);
return data;
});

View File

@ -7,16 +7,11 @@ import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
import { compareObjectsByKeys } from '@/lib/objects';
import { useMemo } from 'react';
import { create } from 'mutative';
import last from 'lodash/last';
export const useIsDashboardChanged = ({ dashboardId }: { dashboardId: string }) => {
const queryClient = useQueryClient();
const originalDashboard = useOriginalDashboardStore((x) => x.getOriginalDashboard(dashboardId));
// const { data: latestVersionNumber } = useGetDashboard(
// { id: dashboardId },
// { select: (x) => last(x.versions)?.version_number }
// );
const { data: currentDashboard, refetch: refetchCurrentDashboard } = useGetDashboard(
{ id: dashboardId, versionNumber: undefined },
{
@ -32,7 +27,7 @@ export const useIsDashboardChanged = ({ dashboardId }: { dashboardId: string })
const onResetDashboardToOriginal = useMemoizedFn(() => {
const options = dashboardQueryKeys.dashboardGetDashboard(
dashboardId,
originalDashboard?.version_number
originalDashboard?.version_number || null
);
const currentDashboard = queryClient.getQueryData<BusterDashboardResponse>(options.queryKey);
if (originalDashboard && currentDashboard) {

View File

@ -1,4 +1,4 @@
import type { IBusterMetric } from '@/api/asset_interfaces/metric';
import type { IBusterMetric, IBusterMetricData } from '@/api/asset_interfaces/metric';
import { useGetMetricVersionNumber } from '@/api/buster_rest/metrics';
import { queryKeys } from '@/api/query_keys';
import { useMemoizedFn } from '@/hooks';
@ -7,7 +7,7 @@ import { useQueryClient } from '@tanstack/react-query';
export const useGetMetricMemoized = () => {
const queryClient = useQueryClient();
const { selectedVersionNumber } = useGetMetricVersionNumber({});
const { selectedVersionNumber } = useGetMetricVersionNumber();
const getMetricMemoized = useMemoizedFn(
(metricId: string, versionNumberProp?: number): IBusterMetric => {
const options = queryKeys.metricsGetMetric(
@ -20,3 +20,19 @@ export const useGetMetricMemoized = () => {
);
return getMetricMemoized;
};
export const useGetMetricDataMemoized = () => {
const queryClient = useQueryClient();
const { selectedVersionNumber, latestVersionNumber } = useGetMetricVersionNumber();
const getMetricDataMemoized = useMemoizedFn(
(metricId: string, versionNumberProp?: number): IBusterMetricData | undefined => {
const options = queryKeys.metricsGetData(
metricId,
versionNumberProp || selectedVersionNumber || latestVersionNumber!
);
const data = queryClient.getQueryData(options.queryKey);
return data;
}
);
return getMetricDataMemoized;
};

View File

@ -30,7 +30,10 @@ export const useIsMetricChanged = ({ metricId }: { metricId: string }) => {
);
const onResetMetricToOriginal = useMemoizedFn(() => {
const options = metricsQueryKeys.metricsGetMetric(metricId, originalMetric?.version_number);
const options = metricsQueryKeys.metricsGetMetric(
metricId,
originalMetric?.version_number || null
);
if (originalMetric) {
queryClient.setQueryData(options.queryKey, originalMetric);
}

View File

@ -17,7 +17,7 @@ import type {
BusterDashboardResponse,
DashboardConfig
} from '@/api/asset_interfaces';
import { DashboardEmptyState } from './DashboardEmptyState';
import { DashboardEmptyState, DashboardNoContentReadOnly } from './DashboardEmptyState';
import { type useUpdateDashboardConfig } from '@/api/buster_rest/dashboards';
import last from 'lodash/last';
@ -136,7 +136,9 @@ export const DashboardContentController: React.FC<{
</DashboardContentControllerProvider>
) : !readOnly ? (
<DashboardEmptyState onOpenAddContentModal={onOpenAddContentModal} />
) : null}
) : (
<DashboardNoContentReadOnly />
)}
</div>
);
}

View File

@ -1,6 +1,7 @@
import { Button } from '@/components/ui/buttons';
import { Plus } from '@/components/ui/icons';
import React from 'react';
import { Text } from '@/components/ui/typography';
export const DashboardEmptyState: React.FC<{
onOpenAddContentModal: () => void;
@ -14,3 +15,12 @@ export const DashboardEmptyState: React.FC<{
);
});
DashboardEmptyState.displayName = 'DashboardEmptyState';
export const DashboardNoContentReadOnly: React.FC = React.memo(() => {
return (
<div className="border-border bg-background flex min-h-56 flex-col items-center justify-center rounded-md border border-dashed shadow">
<Text variant="secondary">No items added to dashboard</Text>
</div>
);
});
DashboardNoContentReadOnly.displayName = 'DashboardNoContentReadOnly';

View File

@ -28,6 +28,7 @@ export const MetricViewChartHeader: React.FC<{
level={4}
readOnly={readOnly}
inputClassName="h-auto!"
placeholder="New chart"
onChange={onSetTitle}>
{name}
</EditableTitle>

View File

@ -11,13 +11,14 @@ import { useQueryClient } from '@tanstack/react-query';
import { useRef } from 'react';
import { didColumnDataChange, simplifyChatConfigForSQLChange } from './helpers';
import { useRunSQL as useRunSQLQuery } from '@/api/buster_rest';
import { useUpdateMetric } from '@/api/buster_rest/metrics';
import { useGetMetricMemoized } from '@/context/Metrics';
import { useGetLatestMetricVersionNumber, useUpdateMetric } from '@/api/buster_rest/metrics';
import { useGetMetricDataMemoized, useGetMetricMemoized } from '@/context/Metrics';
import { timeout } from '@/lib';
export const useMetricRunSQL = () => {
const queryClient = useQueryClient();
const getMetricMemoized = useGetMetricMemoized();
const getMetricDataMemoized = useGetMetricDataMemoized();
const { mutateAsync: stageMetric } = useUpdateMetric({
updateVersion: false,
saveToServer: false,
@ -38,13 +39,7 @@ export const useMetricRunSQL = () => {
isPending: isRunningSQL
} = useRunSQLQuery();
const { openSuccessNotification } = useBusterNotifications();
const getDataByMetricIdMemoized = useMemoizedFn(
(metricId: string): IBusterMetricData | undefined => {
const options = queryKeys.metricsGetData(metricId);
return queryClient.getQueryData(options.queryKey);
}
);
const getLatestMetricVersion = useGetLatestMetricVersionNumber();
const originalConfigs = useRef<{
chartConfig: IBusterMetricChartConfig;
@ -64,8 +59,9 @@ export const useMetricRunSQL = () => {
data_metadata: BusterMetricData['data_metadata'];
isDataFromRerun: boolean;
}) => {
const options = queryKeys.metricsGetData(metricId);
const currentData = getDataByMetricIdMemoized(metricId);
const latestVersionNumber = getLatestMetricVersion(metricId);
const options = queryKeys.metricsGetData(metricId, latestVersionNumber);
const currentData = getMetricDataMemoized(metricId, latestVersionNumber);
if (!currentData) return;
const setter = isDataFromRerun ? 'dataFromRerun' : 'data';
@ -82,7 +78,7 @@ export const useMetricRunSQL = () => {
if (metricId) {
const { data, data_metadata } = d;
const metricMessage = getMetricMemoized(metricId);
const currentMessageData = getDataByMetricIdMemoized(metricId);
const currentMessageData = getMetricDataMemoized(metricId);
if (!originalConfigs.current) {
originalConfigs.current = {
chartConfig: metricMessage?.chart_config!,
@ -185,7 +181,8 @@ export const useMetricRunSQL = () => {
});
await timeout(50);
const currentData = getDataByMetricIdMemoized(metricId);
const latestVersionNumber = getLatestMetricVersion(metricId);
const currentData = getMetricDataMemoized(metricId, latestVersionNumber);
if (currentData?.data_metadata && currentData?.dataFromRerun) {
onSetDataForMetric({

View File

@ -21,6 +21,7 @@ interface AssetAccess {
hasAccess: boolean;
passwordRequired: boolean;
isPublic: boolean;
isDeleted: boolean;
}
interface AssetQueryResult {
@ -32,14 +33,18 @@ interface AssetQueryResult {
const getAssetAccess = (error: RustApiError | null): AssetAccess => {
if (!error) {
return { hasAccess: true, passwordRequired: false, isPublic: false };
return { hasAccess: true, passwordRequired: false, isPublic: false, isDeleted: false };
}
if (error.status === 418) {
return { hasAccess: false, passwordRequired: true, isPublic: true };
return { hasAccess: false, passwordRequired: true, isPublic: true, isDeleted: false };
}
return { hasAccess: false, passwordRequired: false, isPublic: false };
if (error.status === 410) {
return { hasAccess: false, passwordRequired: false, isPublic: false, isDeleted: true };
}
return { hasAccess: false, passwordRequired: false, isPublic: false, isDeleted: false };
};
const useVersionNumber = (props: UseGetAssetProps) => {

View File

@ -31,6 +31,7 @@ export const ChatHeaderTitle: React.FC<{
className="flex w-full items-center overflow-hidden">
<EditableTitle
className="w-full"
placeholder="New chat"
level={5}
id={CHAT_HEADER_TITLE_ID}
onChange={(value) =>