add linking

This commit is contained in:
Nate Kelley 2025-08-02 23:58:14 -06:00
parent 08d7014725
commit 46f812d823
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
9 changed files with 96 additions and 19 deletions

View File

@ -65,16 +65,19 @@ export const prefetchGetReportsList = async (
/**
* Hook to get an individual report by ID
*/
export const useGetReport = (
export const useGetReport = <T = GetReportIndividualResponse>(
reportId: string | undefined,
options?: Omit<UseQueryOptions<GetReportIndividualResponse, RustApiError>, 'queryKey' | 'queryFn'>
options?: Omit<
UseQueryOptions<GetReportIndividualResponse, RustApiError, T>,
'queryKey' | 'queryFn'
>
) => {
const queryFn = useMemoizedFn(() => {
return getReportById(reportId!);
});
return useQuery({
...queryKeys.reportsGetById(reportId!),
...queryKeys.reportsGetReport(reportId!),
queryFn,
enabled: !!reportId,
select: options?.select,
@ -89,7 +92,7 @@ export const prefetchGetReportById = async (reportId: string, queryClientProp?:
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...queryKeys.reportsGetById(reportId),
...queryKeys.reportsGetReport(reportId),
queryFn: () => getReportById_server(reportId)
});
@ -112,18 +115,18 @@ export const useUpdateReport = () => {
onMutate: async ({ reportId, data }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({
queryKey: queryKeys.reportsGetById(reportId).queryKey
queryKey: queryKeys.reportsGetReport(reportId).queryKey
});
// Snapshot the previous value
const previousReport = queryClient.getQueryData<GetReportIndividualResponse>(
queryKeys.reportsGetById(reportId).queryKey
queryKeys.reportsGetReport(reportId).queryKey
);
// Optimistically update the individual report
if (previousReport) {
queryClient.setQueryData(
queryKeys.reportsGetById(reportId).queryKey,
queryKeys.reportsGetReport(reportId).queryKey,
create(previousReport, (draft) => {
if (data.name !== undefined) draft.name = data.name;
if (data.description !== undefined) draft.description = data.description;
@ -141,14 +144,14 @@ export const useUpdateReport = () => {
// If the mutation fails, use the context to roll back
if (context?.previousReport) {
queryClient.setQueryData(
queryKeys.reportsGetById(reportId).queryKey,
queryKeys.reportsGetReport(reportId).queryKey,
context.previousReport
);
}
},
onSuccess: (data, { reportId, data: updateData }) => {
// Update the individual report cache with server response
queryClient.setQueryData(queryKeys.reportsGetById(reportId).queryKey, data);
queryClient.setQueryData(queryKeys.reportsGetReport(reportId).queryKey, data);
const nameChanged = updateData.name !== undefined && updateData.name !== data.name;

View File

@ -13,7 +13,7 @@ const reportsGetList = (filters?: GetReportsListRequest) =>
initialDataUpdatedAt: 0
});
const reportsGetById = (reportId: string) =>
const reportsGetReport = (reportId: string) =>
queryOptions<GetReportIndividualResponse>({
queryKey: ['reports', 'get', reportId] as const,
staleTime: 60 * 1000 // 60 seconds
@ -21,5 +21,5 @@ const reportsGetById = (reportId: string) =>
export const reportsQueryKeys = {
reportsGetList,
reportsGetById
reportsGetReport
};

View File

@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import React, { useMemo } from 'react';
import type { ContextMenuProps } from '../../context-menu/ContextMenu';
import { BusterList } from './index';
import type { BusterListRow } from './interfaces';
import type { BusterListColumn, BusterListRow } from './interfaces';
const meta: Meta<typeof BusterList> = {
title: 'UI/List/BusterList',
@ -33,10 +33,19 @@ const meta: Meta<typeof BusterList> = {
export default meta;
type Story = StoryObj<typeof BusterList>;
// Define the data type for our rows
type SampleData = {
name: string;
age: number;
address?: string;
email?: string;
actions?: any; // For the actions column
};
type Story = StoryObj<typeof BusterList<SampleData>>;
// Sample data for the stories
const sampleColumns = [
const sampleColumns: BusterListColumn<SampleData>[] = [
{
dataIndex: 'name',
title: 'Name',
@ -51,7 +60,9 @@ const sampleColumns = [
dataIndex: 'actions',
title: 'Actions',
width: 100,
render: (_: any, record: any) => <button className="text-blue-500 hover:underline">View</button>
render: (_: any, record: SampleData) => (
<button className="text-blue-500 hover:underline">View</button>
)
}
];

View File

@ -45,6 +45,11 @@ const useBusterAssets = () => {
queryKey: queryKeys.chatsGetChat(assetId).queryKey,
refetchType: 'all'
});
} else if (type === 'report') {
await queryClient.invalidateQueries({
queryKey: queryKeys.reportsGetReport(assetId).queryKey,
refetchType: 'all'
});
} else {
const exhaustiveCheck: never = type;
}

View File

@ -167,6 +167,9 @@ export const useGetAsset = (
isError: reportIsError,
showLoader: !reportIsFetched && !reportIsError
};
case 'chat':
// Chat type is not supported in this hook
return { isFetched: true, error: null, isError: false, showLoader: false };
default: {
const exhaustiveCheck: never = props.type;
return { isFetched: false, error: null, isError: false, showLoader: false };
@ -182,15 +185,28 @@ export const useGetAsset = (
dashboardIsError,
collectionIsFetched,
collectionError,
collectionIsError
collectionIsError,
reportIsFetched,
reportError,
reportIsError
]);
const title = useMemo(() => {
if (isMetric) return metricTitle;
if (isDashboard) return dashboardTitle;
if (isCollection) return collectionTitle;
if (isReport) return reportTitle;
return undefined;
}, [isMetric, isDashboard, isCollection, metricTitle, dashboardTitle, collectionTitle]);
}, [
isMetric,
isDashboard,
isCollection,
isReport,
metricTitle,
dashboardTitle,
collectionTitle,
reportTitle
]);
const { hasAccess, passwordRequired, isPublic } = getAssetAccess(currentQuery.error);

View File

@ -35,6 +35,12 @@ export const CreateChatButton = React.memo(
dashboardId: assetId,
chatId: result.id
});
} else if (assetType === 'report') {
await onChangePage({
route: BusterRoutes.APP_CHAT_ID_REPORT_ID,
reportId: assetId,
chatId: result.id
});
} else {
const _exhaustiveCheck: never = assetType;
}

View File

@ -9,6 +9,7 @@ import { createMetricRoute, type MetricRouteParams } from './createMetricRoute';
import { createDashboardRoute, type DashboardRouteParams } from './createDashboardRoute';
import { createReasoningRoute } from './createReasoningRoute';
import { createDatasetRoute } from './createDatasetRoute';
import { createReportRoute } from './createReportRoute';
type UnionOfFileTypes = FileType | ReasoningFileType | ReasoingMessage_ThoughtFileType;
@ -22,7 +23,7 @@ type OtherRouteParams = {
dashboardVersionNumber?: number; //if this is provided, it will be used instead of versionNumber
page?: undefined;
secondaryView?: undefined | null | string;
type: Exclude<UnionOfFileTypes, 'metric' | 'dashboard'>;
type: UnionOfFileTypes;
};
type BaseParams = MetricRouteParams | DashboardRouteParams | OtherRouteParams;
@ -73,6 +74,13 @@ export const assetParamsToRoute = ({
});
}
if (type === 'report') {
return createReportRoute({
assetId,
chatId
});
}
if (type === 'reasoning') {
return createReasoningRoute({
assetId,

View File

@ -0,0 +1,27 @@
import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes';
export type ReportRouteParams = {
assetId: string;
chatId?: string;
type: 'report';
};
export const createReportRoute = ({
assetId: reportId,
chatId
}: Omit<ReportRouteParams, 'type'>) => {
// Report routes within a chat context
if (chatId) {
return createBusterRoute({
route: BusterRoutes.APP_CHAT_ID_REPORT_ID,
chatId,
reportId
});
}
// Standalone report route
return createBusterRoute({
route: BusterRoutes.APP_REPORTS_ID,
reportId
});
};

View File

@ -29,7 +29,7 @@ const ResponseMessage_FileMetadataSchema = z.object({
timestamp: z.number().optional(),
});
const ResponseMessageFileTypeSchema = z.enum(['metric', 'dashboard', 'reasoning']);
const ResponseMessageFileTypeSchema = z.enum(['metric', 'dashboard', 'reasoning', 'report']);
const ResponseMessage_FileSchema = z.object({
id: z.string(),
@ -59,6 +59,7 @@ const ReasoningMessage_TextSchema = z.object({
const ReasoningFileTypeSchema = z.enum([
'metric',
'dashboard',
'report',
'reasoning',
'agent-action',
'todo',