dashboard pass the version number

This commit is contained in:
Nate Kelley 2025-04-08 22:08:10 -06:00
parent f3a8dbd89d
commit a05a81feaf
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
12 changed files with 101 additions and 56 deletions

View File

@ -36,36 +36,41 @@ import { RustApiError } from '../../buster_rest/errors';
export const useGetMetric = <TData = IBusterMetric>( export const useGetMetric = <TData = IBusterMetric>(
{ {
id, id,
version_number: version_number_prop versionNumber: versionNumberProp
}: { }: {
id: string | undefined; id: string | undefined;
version_number?: number | null; //if null it will not use a params from the query params versionNumber?: number | null; //if null it will not use a params from the query params
}, },
params?: Omit<UseQueryOptions<IBusterMetric, RustApiError, TData>, 'queryKey' | 'queryFn'> params?: Omit<UseQueryOptions<IBusterMetric, RustApiError, TData>, 'queryKey' | 'queryFn'>
) => { ) => {
const queryClient = useQueryClient();
const setOriginalMetric = useOriginalMetricStore((x) => x.setOriginalMetric); const setOriginalMetric = useOriginalMetricStore((x) => x.setOriginalMetric);
const { versionNumber: versionNumberPathParam } = useParams() as {
versionNumber: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('versionNumber');
const versionNumber = versionNumberQueryParam || versionNumberPathParam;
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 { versionNumber: versionNumberPathParam, metricId: metricIdPathParam } = useParams() as {
versionNumber: string | undefined;
metricId: string | undefined;
};
const versionNumberQueryParam = useSearchParams().get('versionNumber');
const versionNumberFromParams = metricIdPathParam
? versionNumberQueryParam || versionNumberPathParam
: undefined;
const version_number = useMemo(() => { const versionNumber = useMemo(() => {
if (version_number_prop === null) return undefined; if (versionNumberProp === null) return undefined;
return version_number_prop || versionNumber ? parseInt(versionNumber!) : undefined; return versionNumberProp || versionNumberFromParams
}, [version_number_prop, versionNumber]); ? parseInt(versionNumberFromParams!)
: undefined;
}, [versionNumberProp, versionNumberFromParams]);
const options = useMemo(() => { const options = useMemo(() => {
return metricsQueryKeys.metricsGetMetric(id!, version_number); return metricsQueryKeys.metricsGetMetric(id!, versionNumber);
}, [id, version_number]); }, [id, versionNumber]);
const queryFn = useMemoizedFn(async () => { const queryFn = useMemoizedFn(async () => {
const result = await getMetric({ id: id!, password, version_number }); const result = await getMetric({ id: id!, password, version_number: versionNumber });
const oldMetric = queryClient.getQueryData(options.queryKey); const oldMetric = queryClient.getQueryData(options.queryKey);
const updatedMetric = upgradeMetricToIMetric(result, oldMetric || null); const updatedMetric = upgradeMetricToIMetric(result, oldMetric || null);
setOriginalMetric(updatedMetric); setOriginalMetric(updatedMetric);
@ -108,32 +113,38 @@ export const useGetMetricsList = (
*/ */
export const useGetMetricData = ({ export const useGetMetricData = ({
id, id,
version_number: version_number_prop versionNumber: versionNumberProp
}: { }: {
id: string | undefined; id: string | undefined;
version_number?: number; versionNumber?: number;
}) => { }) => {
const { const {
isFetched: isFetchedMetric, isFetched: isFetchedMetric,
error: errorMetric, error: errorMetric,
data: fetchedMetricData data: fetchedMetricData
} = useGetMetric({ id }, { select: (x) => x.id }); } = useGetMetric({ id }, { select: (x) => x.id });
const { versionNumber: versionNumberPathParam } = useParams() as { const { versionNumber: versionNumberPathParam, metricId: metricIdPathParam } = useParams() as {
versionNumber: string | undefined; versionNumber: string | undefined;
metricId: string | undefined;
}; };
const versionNumberQueryParam = useSearchParams().get('versionNumber'); const versionNumberQueryParam = useSearchParams().get('versionNumber');
const versionNumber = versionNumberQueryParam || versionNumberPathParam; const versionNumberFromParams = metricIdPathParam
? versionNumberQueryParam || versionNumberPathParam
: undefined;
const version_number = useMemo(() => { const versionNumber = useMemo(() => {
return version_number_prop || versionNumber ? parseInt(versionNumber!) : undefined; if (versionNumberProp === null) return undefined;
}, [version_number_prop, versionNumber]); return versionNumberProp || versionNumberFromParams
? parseInt(versionNumberFromParams!)
: undefined;
}, [versionNumberProp, versionNumberFromParams]);
const queryFn = useMemoizedFn(() => { const queryFn = useMemoizedFn(() => {
return getMetricData({ id: id!, version_number }); return getMetricData({ id: id!, version_number: versionNumber });
}); });
return useQuery({ return useQuery({
...metricsQueryKeys.metricsGetData(id!, version_number), ...metricsQueryKeys.metricsGetData(id!, versionNumber),
queryFn, queryFn,
enabled: () => { enabled: () => {
return !!id && isFetchedMetric && !errorMetric && !!fetchedMetricData; return !!id && isFetchedMetric && !errorMetric && !!fetchedMetricData;

View File

@ -17,7 +17,7 @@ const dashboardGetList = (
const dashboardGetDashboard = (dashboardId: string, version_number?: number) => const dashboardGetDashboard = (dashboardId: string, version_number?: number) =>
queryOptions<BusterDashboardResponse>({ queryOptions<BusterDashboardResponse>({
queryKey: ['dashboard', 'get', dashboardId, version_number] as const, queryKey: ['dashboard', 'get', dashboardId, version_number || 'latest'] as const,
staleTime: 10 * 1000 staleTime: 10 * 1000
}); });

View File

@ -1,4 +1,10 @@
export default async function Page({ params }: { params: Promise<{ versionNumber: string }> }) { import { DashboardViewDashboardController } from '@/controllers/DashboardController/DashboardViewDashboardController/DashboardViewDashboardController';
const { versionNumber } = await params;
return <div>Version {versionNumber}</div>; export default async function Page({
params
}: {
params: Promise<{ versionNumber: string; dashboardId: string; chatId: string }>;
}) {
const { versionNumber, dashboardId, chatId } = await params;
return <DashboardViewDashboardController dashboardId={dashboardId} chatId={chatId} readOnly />;
} }

View File

@ -1,9 +1,11 @@
import { MetricViewChart } from '@/controllers/MetricController/MetricViewChart';
export default async function MetricVersionPage({ export default async function MetricVersionPage({
params params
}: { }: {
params: Promise<{ versionNumber: string }>; params: Promise<{ versionNumber: string; metricId: string }>;
}) { }) {
const { versionNumber } = await params; const { versionNumber, metricId } = await params;
return <div>MetricVersionPage {versionNumber}</div>; return <MetricViewChart metricId={metricId} />;
} }

View File

@ -1,8 +1,10 @@
import { DashboardViewDashboardController } from '@/controllers/DashboardController/DashboardViewDashboardController';
export default async function DashboardVersionPage({ export default async function DashboardVersionPage({
params params
}: { }: {
params: Promise<{ versionNumber: string }>; params: Promise<{ versionNumber: string; dashboardId: string }>;
}) { }) {
const { versionNumber } = await params; const { versionNumber, dashboardId } = await params;
return <div>DashboardVersionPage {versionNumber}</div>; return <DashboardViewDashboardController dashboardId={dashboardId} chatId={undefined} readOnly />;
} }

View File

@ -1,8 +1,10 @@
import { MetricViewChart } from '@/controllers/MetricController/MetricViewChart';
export default async function MetricVersionPage({ export default async function MetricVersionPage({
params params
}: { }: {
params: Promise<{ versionNumber: string }>; params: Promise<{ versionNumber: string; metricId: string }>;
}) { }) {
const { versionNumber } = await params; const { versionNumber, metricId } = await params;
return <div>MetricVersionPage {versionNumber}</div>; return <MetricViewChart metricId={metricId} />;
} }

View File

@ -137,7 +137,7 @@ export const BusterResizeColumns: React.FC<ContainerProps> = ({
return ( return (
<SortableContext id={rowId} items={items} disabled={false}> <SortableContext id={rowId} items={items} disabled={false}>
<div ref={setNodeRef} className="relative h-full w-full"> <div ref={setNodeRef} className="buster-resize-columns relative h-full w-full">
<BusterDragColumnMarkers <BusterDragColumnMarkers
isDraggingIndex={columnMarkerColumnIndex} isDraggingIndex={columnMarkerColumnIndex}
itemsLength={items.length} itemsLength={items.length}

View File

@ -18,12 +18,12 @@ import {
import { arrayMove } from '@dnd-kit/sortable'; import { arrayMove } from '@dnd-kit/sortable';
import { BusterSortableOverlay } from './_BusterSortableOverlay'; import { BusterSortableOverlay } from './_BusterSortableOverlay';
import { BusterResizeableGridRow } from './interfaces'; import { BusterResizeableGridRow } from './interfaces';
import clsx from 'clsx';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import { BusterResizeRows } from './BusterResizeRows'; import { BusterResizeRows } from './BusterResizeRows';
import { NUMBER_OF_COLUMNS, NEW_ROW_ID, MIN_ROW_HEIGHT, TOP_SASH_ID } from './helpers'; import { NUMBER_OF_COLUMNS, NEW_ROW_ID, MIN_ROW_HEIGHT, TOP_SASH_ID } from './helpers';
import { cn } from '@/lib/utils';
const measuringConfig = { const measuringConfig = {
droppable: { droppable: {
@ -331,7 +331,7 @@ export const BusterResizeableGrid: React.FC<{
onDragCancel={onDragCancel} onDragCancel={onDragCancel}
collisionDetection={collisionDetectionStrategy} collisionDetection={collisionDetectionStrategy}
sensors={sensors}> sensors={sensors}>
<div className={clsx('h-full w-full', 'buster-resizeable-grid', className)}> <div className={cn('buster-resizeable-grid h-full w-full', className)}>
<BusterResizeRows <BusterResizeRows
rows={rows} rows={rows}
className={className} className={className}

View File

@ -19,6 +19,7 @@ import type {
} from '@/api/asset_interfaces'; } from '@/api/asset_interfaces';
import { DashboardEmptyState } from './DashboardEmptyState'; import { DashboardEmptyState } from './DashboardEmptyState';
import { type useUpdateDashboardConfig } from '@/api/buster_rest/dashboards'; import { type useUpdateDashboardConfig } from '@/api/buster_rest/dashboards';
import last from 'lodash/last';
const DEFAULT_EMPTY_ROWS: DashboardConfig['rows'] = []; const DEFAULT_EMPTY_ROWS: DashboardConfig['rows'] = [];
const DEFAULT_EMPTY_METRICS: Record<string, BusterMetric> = {}; const DEFAULT_EMPTY_METRICS: Record<string, BusterMetric> = {};
@ -61,6 +62,12 @@ export const DashboardContentController: React.FC<{
return { return {
...row, ...row,
items: row.items.map((item) => { items: row.items.map((item) => {
const selectedMetric = metrics[item.id];
const versionNumber =
last(selectedMetric.versions)?.version_number === selectedMetric.version_number
? undefined
: selectedMetric.version_number;
return { return {
...item, ...item,
children: ( children: (
@ -71,6 +78,7 @@ export const DashboardContentController: React.FC<{
readOnly={readOnly} readOnly={readOnly}
chatId={chatId} chatId={chatId}
numberOfMetrics={numberOfMetrics} numberOfMetrics={numberOfMetrics}
versionNumber={versionNumber}
/> />
) )
}; };
@ -104,6 +112,8 @@ export const DashboardContentController: React.FC<{
} }
}, [dashboard?.id, remapMetrics]); }, [dashboard?.id, remapMetrics]);
console.log(hasMetrics, dashboardRows, dashboard);
return ( return (
<div className="dashboard-content-controller"> <div className="dashboard-content-controller">
{hasMetrics && !!dashboardRows.length && !!dashboard ? ( {hasMetrics && !!dashboardRows.length && !!dashboard ? (
@ -118,11 +128,12 @@ export const DashboardContentController: React.FC<{
draggingId && ( draggingId && (
<DashboardMetricItem <DashboardMetricItem
metricId={draggingId} metricId={draggingId}
readOnly={false} readOnly={true}
dashboardId={dashboard.id} dashboardId={dashboard.id}
isDragOverlay isDragOverlay
numberOfMetrics={numberOfMetrics} numberOfMetrics={numberOfMetrics}
chatId={undefined} chatId={undefined}
versionNumber={metrics[draggingId]?.version_number}
/> />
) )
} }

View File

@ -9,6 +9,7 @@ import { BusterChart } from '@/components/ui/charts/BusterChart';
const DashboardMetricItemBase: React.FC<{ const DashboardMetricItemBase: React.FC<{
metricId: string; metricId: string;
versionNumber: number | undefined;
chatId: string | undefined; chatId: string | undefined;
dashboardId: string; dashboardId: string;
numberOfMetrics: number; numberOfMetrics: number;
@ -18,6 +19,7 @@ const DashboardMetricItemBase: React.FC<{
}> = ({ }> = ({
readOnly, readOnly,
dashboardId, dashboardId,
versionNumber,
className = '', className = '',
metricId, metricId,
isDragOverlay = false, isDragOverlay = false,
@ -32,7 +34,7 @@ const DashboardMetricItemBase: React.FC<{
initialAnimationEnded, initialAnimationEnded,
setInitialAnimationEnded, setInitialAnimationEnded,
isFetchedMetricData isFetchedMetricData
} = useDashboardMetric({ metricId }); } = useDashboardMetric({ metricId, versionNumber });
const loadingMetricData = !!metric && !isFetchedMetricData; const loadingMetricData = !!metric && !isFetchedMetricData;
const chartOptions = metric?.chart_config; const chartOptions = metric?.chart_config;
@ -69,8 +71,6 @@ const DashboardMetricItemBase: React.FC<{
setInitialAnimationEnded(metricId); setInitialAnimationEnded(metricId);
}); });
if (!chartOptions) return null;
return ( return (
<Card <Card
ref={conatinerRef} ref={conatinerRef}
@ -96,7 +96,7 @@ const DashboardMetricItemBase: React.FC<{
isTable ? '' : 'p-3', isTable ? '' : 'p-3',
isDragOverlay ? 'pointer-events-none' : 'pointer-events-auto' isDragOverlay ? 'pointer-events-none' : 'pointer-events-auto'
)}> )}>
{renderChart && ( {renderChart && chartOptions && (
<BusterChart <BusterChart
data={data} data={data}
loading={loading} loading={loading}
@ -115,5 +115,9 @@ const DashboardMetricItemBase: React.FC<{
}; };
export const DashboardMetricItem = React.memo(DashboardMetricItemBase, (prev, next) => { export const DashboardMetricItem = React.memo(DashboardMetricItemBase, (prev, next) => {
return prev.metricId === next.metricId && prev.dashboardId === next.dashboardId; return (
prev.metricId === next.metricId &&
prev.dashboardId === next.dashboardId &&
prev.versionNumber === next.versionNumber
);
}); });

View File

@ -3,9 +3,15 @@ import { useEffect, useMemo, useRef } from 'react';
import { useInViewport } from '@/hooks'; 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,
versionNumber
}: {
metricId: string;
versionNumber: number | undefined;
}) => {
const { data: metric, isFetched: isMetricFetched } = useGetMetric( const { data: metric, isFetched: isMetricFetched } = useGetMetric(
{ id: metricId }, { id: metricId, versionNumber },
{ {
select: ({ select: ({
name, name,
@ -32,7 +38,7 @@ export const useDashboardMetric = ({ metricId }: { metricId: string }) => {
data: metricData, data: metricData,
isFetched: isFetchedMetricData, isFetched: isFetchedMetricData,
dataUpdatedAt: metricDataUpdatedAt dataUpdatedAt: metricDataUpdatedAt
} = useGetMetricData({ id: metricId }); } = useGetMetricData({ id: metricId, versionNumber });
const dashboard = useDashboardContentControllerContextSelector(({ dashboard }) => dashboard); const dashboard = useDashboardContentControllerContextSelector(({ dashboard }) => dashboard);
const metricMetadata = useDashboardContentControllerContextSelector( const metricMetadata = useDashboardContentControllerContextSelector(
({ metricMetadata }) => metricMetadata[metricId] ({ metricMetadata }) => metricMetadata[metricId]

View File

@ -10,23 +10,24 @@ export const DashboardLayoutContainer: React.FC<{
children: React.ReactNode; children: React.ReactNode;
dashboardId: string; dashboardId: string;
}> = ({ children, dashboardId }) => { }> = ({ children, dashboardId }) => {
const { data: permission } = useGetDashboard(
{ id: dashboardId },
{ select: (x) => x.permission }
);
const isEditor = canEdit(permission);
return ( return (
<> <>
{children} {children}
{isEditor && <MemoizedAddToDashboardModal dashboardId={dashboardId} />} <MemoizedAddToDashboardModal dashboardId={dashboardId} />
</> </>
); );
}; };
const MemoizedAddToDashboardModal = React.memo(({ dashboardId }: { dashboardId: string }) => { const MemoizedAddToDashboardModal = React.memo(({ dashboardId }: { dashboardId: string }) => {
const { data: permission } = useGetDashboard(
{ id: dashboardId },
{ select: (x) => x.permission }
);
const isEditor = canEdit(permission);
const { openAddContentModal, onCloseAddContentModal } = useDashboardContentStore(); const { openAddContentModal, onCloseAddContentModal } = useDashboardContentStore();
if (!isEditor) return null;
return ( return (
<AddToDashboardModal <AddToDashboardModal
open={openAddContentModal} open={openAddContentModal}