update dashboard links

This commit is contained in:
Nate Kelley 2025-07-07 11:37:25 -06:00
parent 48678dd226
commit 9d5850cc07
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 143 additions and 157 deletions

View File

@ -6,5 +6,8 @@
}, },
"[css]": { "[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
} }
} }

View File

@ -33,7 +33,7 @@
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@electric-sql/client": "^1.0.6", "@electric-sql/client": "^1.0.6",
"@electric-sql/react": "^1.0.5", "@electric-sql/react": "^1.0.6",
"@faker-js/faker": "^9.9.0", "@faker-js/faker": "^9.9.0",
"@llm-ui/code": "^0.13.3", "@llm-ui/code": "^0.13.3",
"@llm-ui/markdown": "^0.13.3", "@llm-ui/markdown": "^0.13.3",

View File

@ -46,14 +46,14 @@ const topItems = (
{ {
label: 'Home', label: 'Home',
icon: <House4 />, icon: <House4 />,
route: BusterRoutes.APP_HOME, route: createBusterRoute({ route: BusterRoutes.APP_HOME }),
id: BusterRoutes.APP_HOME, id: BusterRoutes.APP_HOME,
active: currentParentRoute === BusterRoutes.APP_HOME active: currentParentRoute === BusterRoutes.APP_HOME
}, },
{ {
label: 'Chat history', label: 'Chat history',
icon: <ASSET_ICONS.chats />, icon: <ASSET_ICONS.chats />,
route: BusterRoutes.APP_CHAT, route: createBusterRoute({ route: BusterRoutes.APP_CHAT }),
id: BusterRoutes.APP_CHAT, id: BusterRoutes.APP_CHAT,
active: isActiveCheck('chat', BusterRoutes.APP_CHAT) active: isActiveCheck('chat', BusterRoutes.APP_CHAT)
} }
@ -75,21 +75,21 @@ const yourStuff = (
{ {
label: 'Metrics', label: 'Metrics',
icon: <ASSET_ICONS.metrics />, icon: <ASSET_ICONS.metrics />,
route: BusterRoutes.APP_METRIC, route: createBusterRoute({ route: BusterRoutes.APP_METRIC }),
id: BusterRoutes.APP_METRIC, id: BusterRoutes.APP_METRIC,
active: isActiveCheck('metric', BusterRoutes.APP_METRIC) active: isActiveCheck('metric', BusterRoutes.APP_METRIC)
}, },
{ {
label: 'Dashboards', label: 'Dashboards',
icon: <ASSET_ICONS.dashboards />, icon: <ASSET_ICONS.dashboards />,
route: BusterRoutes.APP_DASHBOARDS, route: createBusterRoute({ route: BusterRoutes.APP_DASHBOARDS }),
id: BusterRoutes.APP_DASHBOARDS, id: BusterRoutes.APP_DASHBOARDS,
active: isActiveCheck('dashboard', BusterRoutes.APP_DASHBOARDS) active: isActiveCheck('dashboard', BusterRoutes.APP_DASHBOARDS)
}, },
{ {
label: 'Collections', label: 'Collections',
icon: <ASSET_ICONS.collections />, icon: <ASSET_ICONS.collections />,
route: BusterRoutes.APP_COLLECTIONS, route: createBusterRoute({ route: BusterRoutes.APP_COLLECTIONS }),
id: BusterRoutes.APP_COLLECTIONS, id: BusterRoutes.APP_COLLECTIONS,
active: isActiveCheck('collection', BusterRoutes.APP_COLLECTIONS) active: isActiveCheck('collection', BusterRoutes.APP_COLLECTIONS)
} }
@ -104,7 +104,7 @@ const adminTools = (currentParentRoute: BusterRoutes): ISidebarGroup => ({
{ {
label: 'Logs', label: 'Logs',
icon: <UnorderedList2 />, icon: <UnorderedList2 />,
route: BusterRoutes.APP_LOGS, route: createBusterRoute({ route: BusterRoutes.APP_LOGS }),
id: BusterRoutes.APP_LOGS, id: BusterRoutes.APP_LOGS,
collapsedTooltip: 'Logs' collapsedTooltip: 'Logs'
}, },
@ -117,7 +117,7 @@ const adminTools = (currentParentRoute: BusterRoutes): ISidebarGroup => ({
{ {
label: 'Datasets', label: 'Datasets',
icon: <Table />, icon: <Table />,
route: BusterRoutes.APP_DATASETS, route: createBusterRoute({ route: BusterRoutes.APP_DATASETS }),
id: BusterRoutes.APP_DATASETS, id: BusterRoutes.APP_DATASETS,
collapsedTooltip: 'Datasets' collapsedTooltip: 'Datasets'
} }

View File

@ -40,10 +40,11 @@ export const DashboardContentController: React.FC<{
metrics = DEFAULT_EMPTY_METRICS, metrics = DEFAULT_EMPTY_METRICS,
onUpdateDashboardConfig onUpdateDashboardConfig
}) => { }) => {
const [draggingId, setDraggingId] = useState<string | null>(null);
const dashboardVersionNumber = dashboard?.version_number;
const dashboardConfig = dashboard?.config || DEFAULT_EMPTY_CONFIG; const dashboardConfig = dashboard?.config || DEFAULT_EMPTY_CONFIG;
const configRows = dashboardConfig?.rows || DEFAULT_EMPTY_ROWS; const configRows = dashboardConfig?.rows || DEFAULT_EMPTY_ROWS;
const hasMetrics = !isEmpty(metrics); const hasMetrics = !isEmpty(metrics);
const [draggingId, setDraggingId] = useState<string | null>(null);
const numberOfMetrics = Object.values(metrics).length; const numberOfMetrics = Object.values(metrics).length;
const remapMetrics = useMemo(() => { const remapMetrics = useMemo(() => {
@ -65,7 +66,8 @@ export const DashboardContentController: React.FC<{
isDragOverlay isDragOverlay
numberOfMetrics={numberOfMetrics} numberOfMetrics={numberOfMetrics}
chatId={undefined} chatId={undefined}
versionNumber={metrics[draggingId]?.version_number} dashboardVersionNumber={dashboardVersionNumber}
metricVersionNumber={metrics[draggingId]?.version_number}
/> />
) )
); );
@ -79,7 +81,7 @@ export const DashboardContentController: React.FC<{
...row, ...row,
items: row.items.map((item) => { items: row.items.map((item) => {
const selectedMetric = metrics[item.id]; const selectedMetric = metrics[item.id];
const versionNumber = selectedMetric.version_number; const metricVersionNumber = selectedMetric.version_number;
return { return {
...item, ...item,
@ -91,7 +93,8 @@ export const DashboardContentController: React.FC<{
readOnly={readOnly} readOnly={readOnly}
chatId={chatId} chatId={chatId}
numberOfMetrics={numberOfMetrics} numberOfMetrics={numberOfMetrics}
versionNumber={versionNumber} metricVersionNumber={metricVersionNumber}
dashboardVersionNumber={dashboardVersionNumber}
/> />
) )
}; };

View File

@ -5,14 +5,14 @@ import { Card, CardHeader } from '@/components/ui/card/CardBase';
import { BusterChart } from '@/components/ui/charts/BusterChart'; import { BusterChart } from '@/components/ui/charts/BusterChart';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
import { BusterRoutes, createBusterRoute } from '@/routes';
import { MetricTitle } from './MetricTitle'; import { MetricTitle } from './MetricTitle';
import { useDashboardMetric } from './useDashboardMetric'; import { useDashboardMetric } from './useDashboardMetric';
import { assetParamsToRoute } from '@/lib/assets'; import { assetParamsToRoute } from '@/lib/assets';
const DashboardMetricItemBase: React.FC<{ const DashboardMetricItemBase: React.FC<{
metricId: string; metricId: string;
versionNumber: number | undefined; metricVersionNumber: number | undefined;
dashboardVersionNumber: number | undefined;
chatId: string | undefined; chatId: string | undefined;
dashboardId: string; dashboardId: string;
numberOfMetrics: number; numberOfMetrics: number;
@ -22,12 +22,13 @@ const DashboardMetricItemBase: React.FC<{
}> = ({ }> = ({
readOnly, readOnly,
dashboardId, dashboardId,
versionNumber, metricVersionNumber,
className = '', className = '',
metricId, metricId,
isDragOverlay = false, isDragOverlay = false,
numberOfMetrics, numberOfMetrics,
chatId chatId,
dashboardVersionNumber
}) => { }) => {
const { const {
conatinerRef, conatinerRef,
@ -39,7 +40,7 @@ const DashboardMetricItemBase: React.FC<{
isFetchedMetricData, isFetchedMetricData,
metricError, metricError,
metricDataError metricDataError
} = useDashboardMetric({ metricId, versionNumber }); } = useDashboardMetric({ metricId, versionNumber: metricVersionNumber });
const loadingMetricData = !!metric && !isFetchedMetricData; const loadingMetricData = !!metric && !isFetchedMetricData;
const chartOptions = metric?.chart_config; const chartOptions = metric?.chart_config;
@ -61,9 +62,10 @@ const DashboardMetricItemBase: React.FC<{
assetId: metricId, assetId: metricId,
chatId, chatId,
dashboardId, dashboardId,
page: 'chart' page: 'chart',
metricVersionNumber
}); });
}, [metricId, chatId, dashboardId]); }, [metricId, chatId, dashboardId, metricVersionNumber]);
const onInitialAnimationEndPreflight = useMemoizedFn(() => { const onInitialAnimationEndPreflight = useMemoizedFn(() => {
setInitialAnimationEnded(metricId); setInitialAnimationEnded(metricId);

View File

@ -71,10 +71,8 @@ export const ThreeDotMenuButton = React.memo(
versionNumber: number | undefined; versionNumber: number | undefined;
}) => { }) => {
const chatId = useChatIndividualContextSelector((x) => x.chatId); const chatId = useChatIndividualContextSelector((x) => x.chatId);
const { openSuccessMessage } = useBusterNotifications();
const { data: permission } = useGetMetric({ id: metricId }, { select: (x) => x.permission }); const { data: permission } = useGetMetric({ id: metricId }, { select: (x) => x.permission });
const openFullScreenMetric = useOpenFullScreenMetric({ metricId, versionNumber }); const openFullScreenMetric = useOpenFullScreenMetric({ metricId, versionNumber });
const onSetSelectedFile = useChatLayoutContextSelector((x) => x.onSetSelectedFile);
const dashboardSelectMenu = useDashboardSelectMenu({ metricId }); const dashboardSelectMenu = useDashboardSelectMenu({ metricId });
const versionHistoryItems = useVersionHistorySelectMenu({ metricId }); const versionHistoryItems = useVersionHistorySelectMenu({ metricId });
const collectionSelectMenu = useCollectionSelectMenu({ metricId }); const collectionSelectMenu = useCollectionSelectMenu({ metricId });
@ -89,6 +87,8 @@ export const ThreeDotMenuButton = React.memo(
const renameMetricMenu = useRenameMetricSelectMenu({ metricId }); const renameMetricMenu = useRenameMetricSelectMenu({ metricId });
const shareMenu = useShareMenuSelectMenu({ metricId }); const shareMenu = useShareMenuSelectMenu({ metricId });
console.log(permission);
const isEditor = canEdit(permission); const isEditor = canEdit(permission);
const isOwnerEffective = getIsEffectiveOwner(permission); const isOwnerEffective = getIsEffectiveOwner(permission);
const isOwner = getIsOwner(permission); const isOwner = getIsOwner(permission);

View File

@ -32,6 +32,8 @@ const MetricSegments: React.FC<FileContainerSegmentProps> = React.memo(
({ selectedFileView, chatId }) => { ({ selectedFileView, chatId }) => {
const metricId = useChatLayoutContextSelector((x) => x.metricId) || ''; const metricId = useChatLayoutContextSelector((x) => x.metricId) || '';
const dashboardId = useChatLayoutContextSelector((x) => x.dashboardId) || ''; const dashboardId = useChatLayoutContextSelector((x) => x.dashboardId) || '';
const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber);
const dashboardVersionNumber = useChatLayoutContextSelector((x) => x.dashboardVersionNumber);
const { error } = useGetMetric({ id: metricId }); const { error } = useGetMetric({ id: metricId });
const segmentOptions: SegmentedItem<FileView>[] = React.useMemo(() => { const segmentOptions: SegmentedItem<FileView>[] = React.useMemo(() => {
@ -44,6 +46,8 @@ const MetricSegments: React.FC<FileContainerSegmentProps> = React.memo(
chatId, chatId,
dashboardId, dashboardId,
assetId: metricId, assetId: metricId,
metricVersionNumber: metricVersionNumber,
dashboardVersionNumber,
type: 'metric' type: 'metric'
}) })
}, },
@ -55,6 +59,8 @@ const MetricSegments: React.FC<FileContainerSegmentProps> = React.memo(
chatId, chatId,
dashboardId, dashboardId,
assetId: metricId, assetId: metricId,
metricVersionNumber,
dashboardVersionNumber,
type: 'metric' type: 'metric'
}) })
}, },
@ -66,11 +72,13 @@ const MetricSegments: React.FC<FileContainerSegmentProps> = React.memo(
chatId, chatId,
dashboardId, dashboardId,
assetId: metricId, assetId: metricId,
metricVersionNumber,
dashboardVersionNumber,
type: 'metric' type: 'metric'
}) })
} }
]; ];
}, [chatId, error, metricId, dashboardId]); }, [chatId, error, metricId, dashboardId, metricVersionNumber, dashboardVersionNumber]);
return <AppSegmented type="button" options={segmentOptions} value={selectedFileView} />; return <AppSegmented type="button" options={segmentOptions} value={selectedFileView} />;
} }

View File

@ -6,15 +6,13 @@ import { createDashboardRoute } from './createDashboardRoute';
import { createReasoningRoute } from './createReasoningRoute'; import { createReasoningRoute } from './createReasoningRoute';
import { createDatasetRoute } from './createDatasetRoute'; import { createDatasetRoute } from './createDatasetRoute';
// Mock all the route creation functions vi.mock('@/routes/busterRoutes', async () => {
vi.mock('@/routes/busterRoutes', () => ({ const actual = await vi.importActual('@/routes/busterRoutes');
BusterRoutes: { return {
APP_CHAT_ID: '/app/chats/:chatId', ...actual,
APP_COLLECTIONS_ID: '/app/collections/:collectionId', createBusterRoute: vi.fn()
APP_TERMS_ID: '/app/terms/:termId' };
}, });
createBusterRoute: vi.fn()
}));
vi.mock('./createMetricRoute', () => ({ vi.mock('./createMetricRoute', () => ({
createMetricRoute: vi.fn() createMetricRoute: vi.fn()
@ -92,7 +90,7 @@ describe('assetParamsToRoute', () => {
assetId: 'metric-123', assetId: 'metric-123',
chatId: 'chat-456', chatId: 'chat-456',
secondaryView: 'chart-edit', secondaryView: 'chart-edit',
versionNumber: 2, metricVersionNumber: 2,
page: 'chart' page: 'chart'
}); });
expect(result).toBe('/mock/metric/route'); expect(result).toBe('/mock/metric/route');
@ -122,7 +120,7 @@ describe('assetParamsToRoute', () => {
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
type: 'dashboard', type: 'dashboard',
versionNumber: 3, dashboardVersionNumber: 3,
page: 'file', page: 'file',
secondaryView: 'version-history' secondaryView: 'version-history'
}); });
@ -130,7 +128,7 @@ describe('assetParamsToRoute', () => {
expect(mockCreateDashboardRoute).toHaveBeenCalledWith({ expect(mockCreateDashboardRoute).toHaveBeenCalledWith({
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
versionNumber: 3, dashboardVersionNumber: 3,
page: 'file', page: 'file',
secondaryView: 'version-history' secondaryView: 'version-history'
}); });

View File

@ -13,9 +13,13 @@ import { createDatasetRoute } from './createDatasetRoute';
type UnionOfFileTypes = FileType | ReasoningFileType | ReasoingMessage_ThoughtFileType; type UnionOfFileTypes = FileType | ReasoningFileType | ReasoingMessage_ThoughtFileType;
type OtherRouteParams = { type OtherRouteParams = {
assetId: string | undefined;
chatId: string | undefined; chatId: string | undefined;
versionNumber?: number; assetId: string | undefined; //will first try and use metricId assuming it is a metric, then dashboardId assuming it is a dashboard, then assetId
metricId?: string; //if this is provided, it will be used instead of assetId
dashboardId?: string; //if this is provided, it will be used instead of assetId
versionNumber?: number; //will first try and use metricVersionNumber assuming it is a metric, then dashboardVersionNumber assuming it is a dashboard, then versionNumber
metricVersionNumber?: number; //if this is provided, it will be used instead of versionNumber
dashboardVersionNumber?: number; //if this is provided, it will be used instead of versionNumber
page?: undefined; page?: undefined;
secondaryView?: undefined | null | string; secondaryView?: undefined | null | string;
type: Exclude<UnionOfFileTypes, 'metric' | 'dashboard'>; type: Exclude<UnionOfFileTypes, 'metric' | 'dashboard'>;
@ -27,10 +31,14 @@ export const assetParamsToRoute = ({
chatId, chatId,
assetId, assetId,
type, type,
versionNumber,
page, page,
secondaryView secondaryView,
...rest
}: BaseParams): string => { }: BaseParams): string => {
const { versionNumber } = rest as OtherRouteParams;
const { metricVersionNumber, dashboardVersionNumber } = rest as MetricRouteParams;
const { metricId, dashboardId } = rest as OtherRouteParams;
if (!assetId && chatId) { if (!assetId && chatId) {
return createBusterRoute({ return createBusterRoute({
route: BusterRoutes.APP_CHAT_ID, route: BusterRoutes.APP_CHAT_ID,
@ -44,19 +52,22 @@ export const assetParamsToRoute = ({
if (type === 'metric') { if (type === 'metric') {
return createMetricRoute({ return createMetricRoute({
assetId, assetId: metricId || assetId,
metricVersionNumber: metricVersionNumber || versionNumber,
chatId, chatId,
secondaryView: secondaryView as MetricFileViewSecondary, secondaryView: secondaryView as MetricFileViewSecondary,
versionNumber, dashboardVersionNumber,
dashboardId,
page: page as MetricRouteParams['page'] page: page as MetricRouteParams['page']
}); });
} }
if (type === 'dashboard') { if (type === 'dashboard') {
return createDashboardRoute({ return createDashboardRoute({
assetId, assetId: dashboardId || assetId,
dashboardVersionNumber: dashboardVersionNumber || versionNumber,
metricVersionNumber,
chatId, chatId,
versionNumber,
page, page,
secondaryView: secondaryView as DashboardFileViewSecondary secondaryView: secondaryView as DashboardFileViewSecondary
}); });

View File

@ -6,16 +6,7 @@ import { BusterRoutes } from '@/routes/busterRoutes';
vi.mock('@/routes/busterRoutes', async () => { vi.mock('@/routes/busterRoutes', async () => {
const actual = await vi.importActual('@/routes/busterRoutes'); const actual = await vi.importActual('@/routes/busterRoutes');
return { return {
...actual, ...actual
createBusterRoute: vi.fn((params) => {
// Simple mock implementation that returns a string representation
const { route, ...args } = params;
const queryParams = Object.entries(args)
.filter(([_, value]) => value !== undefined)
.map(([key, value]) => `${key}=${value}`)
.join('&');
return queryParams ? `${route}?${queryParams}` : route;
})
}; };
}); });
@ -30,29 +21,28 @@ describe('createDashboardRoute', () => {
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
secondaryView: 'version-history', secondaryView: 'version-history',
versionNumber: 5, dashboardVersionNumber: 5,
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); console.log('result', result);
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123'); expect(result).toBe(
expect(result).toContain('dashboardVersionNumber=5'); '/app/chats/chat-456/dashboards/dashboard-123?secondary_view=version-history&dashboard_version_number=5'
expect(result).toContain('secondaryView=version-history'); );
}); });
it('should create chat dashboard route with version number only', () => { it('should create chat dashboard route with version number only', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
versionNumber: 3, dashboardVersionNumber: 3,
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-123?dashboard_version_number=3'
expect(result).toContain('dashboardId=dashboard-123'); );
expect(result).toContain('dashboardVersionNumber=3');
}); });
it('should create chat dashboard route with version-history secondary view', () => { it('should create chat dashboard route with version-history secondary view', () => {
@ -63,10 +53,9 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-123?secondary_view=version-history'
expect(result).toContain('dashboardId=dashboard-123'); );
expect(result).toContain('secondaryView=version-history');
}); });
it('should create chat dashboard route with minimal parameters', () => { it('should create chat dashboard route with minimal parameters', () => {
@ -76,21 +65,17 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe('/app/chats/chat-456/dashboards/dashboard-123');
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123');
}); });
it('should create non-chat dashboard route with version number', () => { it('should create non-chat dashboard route with version number', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
versionNumber: 7, dashboardVersionNumber: 7,
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123?dashboard_version_number=7');
expect(result).toContain('dashboardId=dashboard-123');
expect(result).toContain('dashboardVersionNumber=7');
}); });
it('should create non-chat dashboard route with version-history secondary view', () => { it('should create non-chat dashboard route with version-history secondary view', () => {
@ -100,9 +85,7 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123?secondary_view=version-history');
expect(result).toContain('dashboardId=dashboard-123');
expect(result).toContain('secondaryView=version-history');
}); });
it('should create non-chat dashboard route with minimal parameters', () => { it('should create non-chat dashboard route with minimal parameters', () => {
@ -111,8 +94,7 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123');
expect(result).toContain('dashboardId=dashboard-123');
}); });
}); });
@ -121,14 +103,13 @@ describe('createDashboardRoute', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
versionNumber: 2, dashboardVersionNumber: 2,
page: 'file' page: 'file'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-123?dashboard_version_number=2'
expect(result).toContain('dashboardId=dashboard-123'); );
expect(result).toContain('dashboardVersionNumber=2');
}); });
it('should create chat dashboard file route with version-history secondary view', () => { it('should create chat dashboard file route with version-history secondary view', () => {
@ -139,10 +120,9 @@ describe('createDashboardRoute', () => {
page: 'file' page: 'file'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-123?secondary_view=version-history'
expect(result).toContain('dashboardId=dashboard-123'); );
expect(result).toContain('secondaryView=version-history');
}); });
it('should create chat dashboard file route without version number', () => { it('should create chat dashboard file route without version number', () => {
@ -152,21 +132,17 @@ describe('createDashboardRoute', () => {
page: 'file' page: 'file'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe('/app/chats/chat-456/dashboards/dashboard-123');
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123');
}); });
it('should create non-chat dashboard file route with version number', () => { it('should create non-chat dashboard file route with version number', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
versionNumber: 8, dashboardVersionNumber: 8,
page: 'file' page: 'file'
}); });
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123?dashboard_version_number=8');
expect(result).toContain('dashboardId=dashboard-123');
expect(result).toContain('dashboardVersionNumber=8');
}); });
it('should create non-chat dashboard file route without version number', () => { it('should create non-chat dashboard file route without version number', () => {
@ -175,8 +151,7 @@ describe('createDashboardRoute', () => {
page: 'file' page: 'file'
}); });
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123');
expect(result).toContain('dashboardId=dashboard-123');
}); });
}); });
@ -188,9 +163,7 @@ describe('createDashboardRoute', () => {
page: undefined page: undefined
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe('/app/chats/chat-456/dashboards/dashboard-123');
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123');
}); });
it('should handle undefined secondary view in chat context', () => { it('should handle undefined secondary view in chat context', () => {
@ -201,9 +174,7 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe('/app/chats/chat-456/dashboards/dashboard-123');
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123');
}); });
it('should handle undefined secondary view in non-chat context', () => { it('should handle undefined secondary view in non-chat context', () => {
@ -213,8 +184,7 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123');
expect(result).toContain('dashboardId=dashboard-123');
}); });
it('should handle undefined page value', () => { it('should handle undefined page value', () => {
@ -224,9 +194,7 @@ describe('createDashboardRoute', () => {
page: undefined page: undefined
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe('/app/chats/chat-456/dashboards/dashboard-123');
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123');
}); });
}); });
@ -238,52 +206,46 @@ describe('createDashboardRoute', () => {
secondaryView: undefined, secondaryView: undefined,
versionNumber: undefined, versionNumber: undefined,
page: 'dashboard' page: 'dashboard'
}); } as any);
expect(result).toContain(BusterRoutes.APP_DASHBOARD_ID); expect(result).toBe('/app/dashboards/dashboard-123');
expect(result).toContain('dashboardId=dashboard-123');
}); });
it('should handle version number 1', () => { it('should handle version number 1', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
versionNumber: 1, dashboardVersionNumber: 1,
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-123?dashboard_version_number=1'
expect(result).toContain('dashboardId=dashboard-123'); );
expect(result).toContain('dashboardVersionNumber=1');
}); });
it('should handle zero version number', () => { it('should handle zero version number', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
versionNumber: 0, dashboardVersionNumber: 0,
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe('/app/chats/chat-456/dashboards/dashboard-123');
expect(result).toContain('chatId=chat-456');
expect(result).toContain('dashboardId=dashboard-123');
expect(result).toContain('dashboardVersionNumber=0');
}); });
it('should handle large version numbers', () => { it('should handle large version numbers', () => {
const result = createDashboardRoute({ const result = createDashboardRoute({
assetId: 'dashboard-123', assetId: 'dashboard-123',
chatId: 'chat-456', chatId: 'chat-456',
versionNumber: 999999, dashboardVersionNumber: 999999,
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-123?dashboard_version_number=999999'
expect(result).toContain('dashboardId=dashboard-123'); );
expect(result).toContain('dashboardVersionNumber=999999');
}); });
it('should handle complex dashboard IDs', () => { it('should handle complex dashboard IDs', () => {
@ -293,9 +255,9 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-456'); '/app/chats/chat-456/dashboards/dashboard-with-special-chars-123_456-789'
expect(result).toContain('dashboardId=dashboard-with-special-chars-123_456-789'); );
}); });
it('should handle complex chat IDs', () => { it('should handle complex chat IDs', () => {
@ -305,9 +267,9 @@ describe('createDashboardRoute', () => {
page: 'dashboard' page: 'dashboard'
}); });
expect(result).toContain(BusterRoutes.APP_CHAT_ID_DASHBOARD_ID); expect(result).toBe(
expect(result).toContain('chatId=chat-with-special-chars-456_789-012'); '/app/chats/chat-with-special-chars-456_789-012/dashboards/dashboard-123'
expect(result).toContain('dashboardId=dashboard-123'); );
}); });
}); });
}); });

View File

@ -4,8 +4,10 @@ import type { DashboardFileViewSecondary } from '../../layouts/ChatLayout/ChatLa
export type DashboardRouteParams = { export type DashboardRouteParams = {
assetId: string; assetId: string;
chatId?: string; chatId?: string;
secondaryView?: DashboardFileViewSecondary;
versionNumber?: number; versionNumber?: number;
secondaryView?: DashboardFileViewSecondary;
dashboardVersionNumber?: number;
metricVersionNumber?: number;
type: 'dashboard'; type: 'dashboard';
page?: 'file' | 'dashboard' | undefined; page?: 'file' | 'dashboard' | undefined;
}; };
@ -14,10 +16,13 @@ export const createDashboardRoute = ({
assetId: dashboardId, assetId: dashboardId,
chatId, chatId,
secondaryView, secondaryView,
versionNumber: dashboardVersionNumber, dashboardVersionNumber: _dashboardVersionNumber,
metricVersionNumber,
versionNumber,
page = 'dashboard' page = 'dashboard'
}: Omit<DashboardRouteParams, 'type'>) => { }: Omit<DashboardRouteParams, 'type'>) => {
const baseParams = { dashboardVersionNumber, dashboardId, secondaryView }; const dashboardVersionNumber = _dashboardVersionNumber || versionNumber;
const baseParams = { dashboardVersionNumber, dashboardId, secondaryView, metricVersionNumber };
if (page === 'dashboard') { if (page === 'dashboard') {
if (chatId) { if (chatId) {

View File

@ -6,9 +6,11 @@ export type MetricRouteParams = {
dashboardId?: string; dashboardId?: string;
chatId?: string; chatId?: string;
secondaryView?: MetricFileViewSecondary; secondaryView?: MetricFileViewSecondary;
versionNumber?: number; metricVersionNumber?: number;
dashboardVersionNumber?: number;
type: 'metric'; type: 'metric';
page?: 'chart' | 'results' | 'sql' | undefined; page?: 'chart' | 'results' | 'sql' | undefined;
versionNumber?: number; //will first try and use metricVersionNumber assuming it is a metric, then dashboardVersionNumber assuming it is a dashboard, then versionNumber
}; };
export const createMetricRoute = ({ export const createMetricRoute = ({
@ -16,10 +18,13 @@ export const createMetricRoute = ({
chatId, chatId,
secondaryView, secondaryView,
dashboardId, dashboardId,
versionNumber: metricVersionNumber, metricVersionNumber: _metricVersionNumber,
dashboardVersionNumber,
versionNumber,
page = 'chart' page = 'chart'
}: Omit<MetricRouteParams, 'type'>) => { }: Omit<MetricRouteParams, 'type'>) => {
const baseParams = { metricVersionNumber, metricId, secondaryView }; const metricVersionNumber = _metricVersionNumber || versionNumber;
const baseParams = { metricVersionNumber, dashboardVersionNumber, metricId, secondaryView };
if (page === 'chart') { if (page === 'chart') {
// Check for dashboardId first (requires chatId as well) // Check for dashboardId first (requires chatId as well)

View File

@ -4,11 +4,11 @@ export enum BusterAppRoutes {
APP_HOME = '/app/home', APP_HOME = '/app/home',
APP_COLLECTIONS = '/app/collections', APP_COLLECTIONS = '/app/collections',
APP_COLLECTIONS_ID = '/app/collections/:collectionId', APP_COLLECTIONS_ID = '/app/collections/:collectionId',
APP_METRIC = '/app/metrics?metric_version_number=:metricVersionNumber', APP_METRIC = '/app/metrics',
APP_METRIC_ID_CHART = '/app/metrics/:metricId/chart?secondary_view=:secondaryView&metric_version_number=:metricVersionNumber', APP_METRIC_ID_CHART = '/app/metrics/:metricId/chart?secondary_view=:secondaryView&metric_version_number=:metricVersionNumber',
APP_METRIC_ID_RESULTS = '/app/metrics/:metricId/results?secondary_view=:secondaryView&metric_version_number=:metricVersionNumber', APP_METRIC_ID_RESULTS = '/app/metrics/:metricId/results?secondary_view=:secondaryView&metric_version_number=:metricVersionNumber',
APP_METRIC_ID_SQL = '/app/metrics/:metricId/sql?metric_version_number=:metricVersionNumber', APP_METRIC_ID_SQL = '/app/metrics/:metricId/sql?metric_version_number=:metricVersionNumber',
APP_DASHBOARDS = '/app/dashboards?dashboard_version_number=:dashboardVersionNumber', APP_DASHBOARDS = '/app/dashboards',
APP_DASHBOARD_ID = '/app/dashboards/:dashboardId?secondary_view=:secondaryView&dashboard_version_number=:dashboardVersionNumber', APP_DASHBOARD_ID = '/app/dashboards/:dashboardId?secondary_view=:secondaryView&dashboard_version_number=:dashboardVersionNumber',
APP_DASHBOARD_ID_FILE = '/app/dashboards/:dashboardId/file?dashboard_version_number=:dashboardVersionNumber&secondary_view=:secondaryView', APP_DASHBOARD_ID_FILE = '/app/dashboards/:dashboardId/file?dashboard_version_number=:dashboardVersionNumber&secondary_view=:secondaryView',
APP_LOGS = '/app/logs', APP_LOGS = '/app/logs',
@ -54,7 +54,6 @@ export type BusterAppRoutesWithArgs = {
}; };
[BusterAppRoutes.APP_METRIC]: { [BusterAppRoutes.APP_METRIC]: {
route: BusterAppRoutes.APP_METRIC; route: BusterAppRoutes.APP_METRIC;
metricVersionNumber?: number;
}; };
[BusterAppRoutes.APP_METRIC_ID_CHART]: { [BusterAppRoutes.APP_METRIC_ID_CHART]: {
route: BusterAppRoutes.APP_METRIC_ID_CHART; route: BusterAppRoutes.APP_METRIC_ID_CHART;
@ -75,7 +74,6 @@ export type BusterAppRoutesWithArgs = {
}; };
[BusterAppRoutes.APP_DASHBOARDS]: { [BusterAppRoutes.APP_DASHBOARDS]: {
route: BusterAppRoutes.APP_DASHBOARDS; route: BusterAppRoutes.APP_DASHBOARDS;
dashboardVersionNumber?: number;
}; };
[BusterAppRoutes.APP_DASHBOARD_ID]: { [BusterAppRoutes.APP_DASHBOARD_ID]: {
route: BusterAppRoutes.APP_DASHBOARD_ID; route: BusterAppRoutes.APP_DASHBOARD_ID;

View File

@ -207,8 +207,8 @@ importers:
specifier: ^1.0.6 specifier: ^1.0.6
version: 1.0.6 version: 1.0.6
'@electric-sql/react': '@electric-sql/react':
specifier: ^1.0.5 specifier: ^1.0.6
version: 1.0.5(react@18.3.1) version: 1.0.6(react@18.3.1)
'@faker-js/faker': '@faker-js/faker':
specifier: ^9.9.0 specifier: ^9.9.0
version: 9.9.0 version: 9.9.0
@ -2005,14 +2005,11 @@ packages:
'@electric-sql/client@1.0.0-beta.1': '@electric-sql/client@1.0.0-beta.1':
resolution: {integrity: sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg==} resolution: {integrity: sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg==}
'@electric-sql/client@1.0.5':
resolution: {integrity: sha512-DO7dvfCbZU6k33vr3ymBCXER6kPpoBODoRBru7oI16B4/ZXlxhMBpsmzmd8p9dQrPICCpQm6bBkNI6qI3oUAIQ==}
'@electric-sql/client@1.0.6': '@electric-sql/client@1.0.6':
resolution: {integrity: sha512-W3vrQhpKeMrOwErnrurC+aXJI8o6g4hSvHdFv10vES4Y+u3zBQoee88otr25GYX8cdleTXlczvh7XQhn4ywRBA==} resolution: {integrity: sha512-W3vrQhpKeMrOwErnrurC+aXJI8o6g4hSvHdFv10vES4Y+u3zBQoee88otr25GYX8cdleTXlczvh7XQhn4ywRBA==}
'@electric-sql/react@1.0.5': '@electric-sql/react@1.0.6':
resolution: {integrity: sha512-mNabbjw0BGV8nw6JUQF6OecQU5IQW8RbJ4SiwQ4LGJ2G0jRIT6cnQsNdXU4amoMnbchRhXFDGb8FJxnEVczn3Q==} resolution: {integrity: sha512-r+45jTP0o4urpn5LF94vIgqcvnBD6MJFi7QwYUjQLVU46gu+43Sudl503Klier10j473emZHIdvrI6FxRWH/Zw==}
peerDependencies: peerDependencies:
react: '>=18.3.1 <20.0.0' react: '>=18.3.1 <20.0.0'
peerDependenciesMeta: peerDependenciesMeta:
@ -13493,21 +13490,15 @@ snapshots:
optionalDependencies: optionalDependencies:
'@rollup/rollup-darwin-arm64': 4.44.2 '@rollup/rollup-darwin-arm64': 4.44.2
'@electric-sql/client@1.0.5':
dependencies:
'@microsoft/fetch-event-source': 2.0.1
optionalDependencies:
'@rollup/rollup-darwin-arm64': 4.44.2
'@electric-sql/client@1.0.6': '@electric-sql/client@1.0.6':
dependencies: dependencies:
'@microsoft/fetch-event-source': 2.0.1 '@microsoft/fetch-event-source': 2.0.1
optionalDependencies: optionalDependencies:
'@rollup/rollup-darwin-arm64': 4.44.2 '@rollup/rollup-darwin-arm64': 4.44.2
'@electric-sql/react@1.0.5(react@18.3.1)': '@electric-sql/react@1.0.6(react@18.3.1)':
dependencies: dependencies:
'@electric-sql/client': 1.0.5 '@electric-sql/client': 1.0.6
use-sync-external-store: 1.5.0(react@18.3.1) use-sync-external-store: 1.5.0(react@18.3.1)
optionalDependencies: optionalDependencies:
react: 18.3.1 react: 18.3.1