create report requests on web

This commit is contained in:
Nate Kelley 2025-08-02 22:05:17 -06:00
parent 729468d5cd
commit b031cec0c4
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 251 additions and 2 deletions

View File

@ -0,0 +1,3 @@
// Export all reports-related functionality
export * from './requests';
export * from './queryRequests';

View File

@ -0,0 +1,163 @@
import {
QueryClient,
type UseQueryOptions,
useMutation,
useQuery,
useQueryClient
} from '@tanstack/react-query';
import { create } from 'mutative';
import { useMemoizedFn } from '@/hooks';
import { queryKeys } from '@/api/query_keys';
import type { RustApiError } from '../errors';
import type {
GetReportsListResponse,
GetReportIndividualResponse,
UpdateReportRequest,
UpdateReportResponse
} from '@buster/server-shared/reports';
import {
getReportsList,
getReportsList_server,
getReportById,
getReportById_server,
updateReport
} from './requests';
/**
* Hook to get a list of reports
*/
export const useGetReportsList = (params?: Parameters<typeof getReportsList>[0]) => {
const queryFn = useMemoizedFn(() => {
return getReportsList(params);
});
const res = useQuery({
...queryKeys.reportsGetList(params),
queryFn
});
return {
...res,
data: res.data || {
data: [],
pagination: { page: 1, page_size: 5000, total: 0, total_pages: 0 }
}
};
};
/**
* Prefetch function for reports list (server-side)
*/
export const prefetchGetReportsList = async (
params?: Parameters<typeof getReportsList>[0],
queryClientProp?: QueryClient
) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...queryKeys.reportsGetList(params),
queryFn: () => getReportsList_server(params)
});
return queryClient;
};
/**
* Hook to get an individual report by ID
*/
export const useGetReportById = (
reportId: string,
options?: Omit<UseQueryOptions<GetReportIndividualResponse, RustApiError>, 'queryKey' | 'queryFn'>
) => {
const queryFn = useMemoizedFn(() => {
return getReportById(reportId);
});
return useQuery({
...queryKeys.reportsGetById(reportId),
queryFn,
enabled: !!reportId,
...options
});
};
/**
* Prefetch function for individual report (server-side)
*/
export const prefetchGetReportById = async (reportId: string, queryClientProp?: QueryClient) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...queryKeys.reportsGetById(reportId),
queryFn: () => getReportById_server(reportId)
});
return queryClient;
};
/**
* Hook to update a report
*/
export const useUpdateReport = () => {
const queryClient = useQueryClient();
return useMutation<
UpdateReportResponse,
RustApiError,
{ reportId: string; data: UpdateReportRequest },
{ previousReport?: GetReportIndividualResponse }
>({
mutationFn: ({ reportId, data }) => updateReport(reportId, data),
onMutate: async ({ reportId, data }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({
queryKey: queryKeys.reportsGetById(reportId).queryKey
});
// Snapshot the previous value
const previousReport = queryClient.getQueryData<GetReportIndividualResponse>(
queryKeys.reportsGetById(reportId).queryKey
);
// Optimistically update the individual report
if (previousReport) {
queryClient.setQueryData(
queryKeys.reportsGetById(reportId).queryKey,
create(previousReport, (draft) => {
if (data.name !== undefined) draft.name = data.name;
if (data.description !== undefined) draft.description = data.description;
if (data.publicly_accessible !== undefined)
draft.publicly_accessible = data.publicly_accessible;
if (data.content !== undefined) draft.content = data.content;
})
);
}
// Return context with previous values
return { previousReport };
},
onError: (_err, { reportId }, context) => {
// If the mutation fails, use the context to roll back
if (context?.previousReport) {
queryClient.setQueryData(
queryKeys.reportsGetById(reportId).queryKey,
context.previousReport
);
}
},
onSuccess: (data, { reportId, data: updateData }) => {
// Update the individual report cache with server response
queryClient.setQueryData(queryKeys.reportsGetById(reportId).queryKey, data);
const nameChanged = updateData.name !== undefined && updateData.name !== data.name;
// Invalidate the list cache to ensure it's fresh
if (nameChanged) {
queryClient.invalidateQueries({
queryKey: queryKeys.reportsGetList().queryKey,
refetchType: 'all'
});
}
}
});
};

View File

@ -0,0 +1,55 @@
import { mainApiV2 } from '../instances';
import { serverFetch } from '@/api/createServerInstance';
import { BASE_URL_V2 } from '../config';
import type {
GetReportsListRequest,
GetReportsListResponse,
GetReportIndividualResponse,
UpdateReportRequest,
UpdateReportResponse
} from '@buster/server-shared/reports';
/**
* Get a list of reports with optional filters
*/
export const getReportsList = async (params?: GetReportsListRequest) => {
const { page = 1, page_size = 5000, ...allParams } = params || {};
return mainApiV2
.get<GetReportsListResponse>('/reports', { params: { page, page_size, ...allParams } })
.then((res) => res.data);
};
/**
* Server-side version of getReportsList
*/
export const getReportsList_server = async (params?: Parameters<typeof getReportsList>[0]) => {
const { page = 1, page_size = 5000, ...allParams } = params || {};
return await serverFetch<GetReportsListResponse>('/reports', {
baseURL: BASE_URL_V2,
params: { page, page_size, ...allParams }
});
};
/**
* Get an individual report by ID
*/
export const getReportById = async (reportId: string) => {
return mainApiV2.get<GetReportIndividualResponse>(`/reports/${reportId}`).then((res) => res.data);
};
/**
* Server-side version of getReportById
*/
export const getReportById_server = async (reportId: string) => {
return await serverFetch<GetReportIndividualResponse>(`/reports/${reportId}`, {
baseURL: BASE_URL_V2,
method: 'GET'
});
};
/**
* Update a report
*/
export const updateReport = async (reportId: string, data: UpdateReportRequest) => {
return mainApiV2.put<UpdateReportResponse>(`/reports/${reportId}`, data).then((res) => res.data);
};

View File

@ -12,6 +12,7 @@ import { userQueryKeys } from './users';
import { securityQueryKeys } from './security';
import { slackQueryKeys } from './slack';
import { dictionariesQueryKeys } from './dictionaries';
import { reportsQueryKeys } from './reports';
export const queryKeys = {
...datasetQueryKeys,
@ -27,5 +28,6 @@ export const queryKeys = {
...permissionGroupQueryKeys,
...securityQueryKeys,
...slackQueryKeys,
...dictionariesQueryKeys
...dictionariesQueryKeys,
...reportsQueryKeys
};

View File

@ -0,0 +1,25 @@
import { queryOptions } from '@tanstack/react-query';
import type {
GetReportsListResponse,
GetReportIndividualResponse,
GetReportsListRequest
} from '@buster/server-shared/reports';
const reportsGetList = (filters?: GetReportsListRequest) =>
queryOptions<GetReportsListResponse>({
queryKey: ['reports', 'list', filters || { page: 1, page_size: 5000 }] as const,
staleTime: 10 * 1000, // 10 seconds
initialData: { data: [], pagination: { page: 1, page_size: 5000, total: 0, total_pages: 0 } },
initialDataUpdatedAt: 0
});
const reportsGetById = (reportId: string) =>
queryOptions<GetReportIndividualResponse>({
queryKey: ['reports', 'get', reportId] as const,
staleTime: 60 * 1000 // 60 seconds
});
export const reportsQueryKeys = {
reportsGetList,
reportsGetById
};

View File

@ -42,7 +42,8 @@ export const persistOptions: PersistQueryClientProviderProps['persistOptions'] =
maxAge: PERSIST_TIME,
dehydrateOptions: {
shouldDehydrateQuery: (query) => {
const isList = query.queryKey[1] === 'list';
const isList =
query.queryKey[1] === 'list' || query.queryKey[query.queryKey.length - 1] === 'list';
return isList || ALL_PERSISTED_QUERIES.includes(query.queryHash);
}
},