relative app segmented

This commit is contained in:
Nate Kelley 2025-08-20 15:01:37 -06:00
parent 14e6164c26
commit 4eca05fb9c
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
15 changed files with 216 additions and 167 deletions

View File

@ -1,5 +1,5 @@
import { type ReportElementsWithIds, getReport } from '@buster/database';
import type { GetReportIndividualResponse, ReportElements } from '@buster/server-shared/reports';
import type { GetReportResponse, ReportElements } from '@buster/server-shared/reports';
import { markdownToPlatejs } from '@buster/server-utils/report';
import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
@ -8,7 +8,7 @@ import { standardErrorHandler } from '../../../../utils/response';
export async function getReportHandler(
reportId: string,
user: { id: string }
): Promise<GetReportIndividualResponse> {
): Promise<GetReportResponse> {
const report = await getReport({ reportId, userId: user.id });
const platejsResult = await markdownToPlatejs(report.content);
@ -19,7 +19,7 @@ export async function getReportHandler(
const content: ReportElementsWithIds = platejsResult.elements as ReportElementsWithIds;
const response: GetReportIndividualResponse = {
const response: GetReportResponse = {
...report,
content,
};
@ -36,7 +36,7 @@ const app = new Hono()
throw new HTTPException(404, { message: 'Report ID is required' });
}
const response: GetReportIndividualResponse = await getReportHandler(reportId, user);
const response: GetReportResponse = await getReportHandler(reportId, user);
return c.json(response);
})
.onError(standardErrorHandler);

View File

@ -1,7 +1,4 @@
import type {
GetReportIndividualResponse,
UpdateReportResponse,
} from '@buster/server-shared/reports';
import type { GetReportResponse, UpdateReportResponse } from '@buster/server-shared/reports';
import {
QueryClient,
type UseQueryOptions,
@ -95,12 +92,9 @@ export const prefetchGetReport = async (
/**
* Hook to get an individual report by ID
*/
export const useGetReport = <T = GetReportIndividualResponse>(
export const useGetReport = <T = GetReportResponse>(
{ reportId, versionNumber }: { reportId: string | undefined; versionNumber?: number },
options?: Omit<
UseQueryOptions<GetReportIndividualResponse, RustApiError, T>,
'queryKey' | 'queryFn'
>
options?: Omit<UseQueryOptions<GetReportResponse, RustApiError, T>, 'queryKey' | 'queryFn'>
) => {
const queryFn = () => {
return getReportById(reportId ?? '');
@ -136,7 +130,7 @@ export const useUpdateReport = () => {
UpdateReportResponse,
RustApiError,
Parameters<typeof updateReport>[0],
{ previousReport?: GetReportIndividualResponse }
{ previousReport?: GetReportResponse }
>({
mutationFn: updateReport,
onMutate: async ({ reportId, ...data }) => {
@ -146,7 +140,7 @@ export const useUpdateReport = () => {
});
// Snapshot the previous value
const previousReport = queryClient.getQueryData<GetReportIndividualResponse>(
const previousReport = queryClient.getQueryData<GetReportResponse>(
reportsQueryKeys.reportsGetReport(reportId, 'LATEST').queryKey
);

View File

@ -1,10 +1,10 @@
import type { GetReportIndividualResponse } from '@buster/server-shared/reports';
import type { GetReportResponse } from '@buster/server-shared/reports';
import { useQuery } from '@tanstack/react-query';
import { useSearch } from '@tanstack/react-router';
import { useMemo } from 'react';
import { reportsQueryKeys } from '@/api/query_keys/reports';
const stableVersionDataSelector = (data: GetReportIndividualResponse) => data.version_number;
const stableVersionDataSelector = (data: GetReportResponse) => data.version_number;
const stableVersionSearchSelector = (state: { report_version_number?: number | undefined }) =>
state.report_version_number;

View File

@ -1,5 +1,5 @@
import type {
GetReportIndividualResponse,
GetReportResponse,
GetReportsListRequest,
GetReportsListResponse,
UpdateReportRequest,
@ -21,7 +21,7 @@ export const getReportsList = async (params?: GetReportsListRequest) => {
* Get an individual report by ID
*/
export const getReportById = async (reportId: string) => {
return mainApiV2.get<GetReportIndividualResponse>(`/reports/${reportId}`).then((res) => res.data);
return mainApiV2.get<GetReportResponse>(`/reports/${reportId}`).then((res) => res.data);
};
/**

View File

@ -1,5 +1,5 @@
import type {
GetReportIndividualResponse,
GetReportResponse,
GetReportsListRequest,
GetReportsListResponse,
} from '@buster/server-shared/reports';
@ -14,7 +14,7 @@ const reportsGetList = (filters?: GetReportsListRequest) =>
});
const reportsGetReport = (reportId: string, versionNumber: number | 'LATEST') =>
queryOptions<GetReportIndividualResponse>({
queryOptions<GetReportResponse>({
queryKey: ['reports', 'get', reportId, versionNumber || 'LATEST'] as const,
staleTime: 60 * 1000, // 60 seconds
});

View File

@ -2,7 +2,6 @@ import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Button } from '@/components/ui/buttons';
import { Command, ReturnKey, TriangleWarning } from '@/components/ui/icons';
import { PreventNavigation } from '@/components/ui/layouts/PreventNavigation';
import { PopupContainer, PopupSplitter } from '@/components/ui/popup';
import { Text } from '@/components/ui/typography';

View File

@ -2,7 +2,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
import { useState } from 'react';
import { Checkbox } from '../checkbox';
import { Grid, PaintRoller, Star, Storage } from '../icons';
import { PreventNavigation } from '../layouts/PreventNavigation';
import { AppSegmented } from './AppSegmented';
const meta: Meta<typeof AppSegmented> = {
@ -130,68 +129,3 @@ export const WithOnlyIcons: Story = {
],
},
};
export const WithPreventDefault: Story = {
args: {
options: [
{
value: 'tab1',
icon: <Star />,
link: {
to: '/app/datasets',
},
label: 'Tab 1',
tooltip: 'Tooltip 1',
},
{
value: 'tab2',
icon: <Grid />,
link: {
to: '/app/datasets',
},
label: 'Tab 2',
tooltip: 'Tooltip 2',
},
{
value: 'tab3',
icon: <Storage />,
link: {
to: '/app/datasets',
},
label: 'Tab 3',
tooltip: 'Tooltip 3',
},
],
},
render: (args) => {
const [isDirty, setIsDirty] = useState(true);
return (
<div className="flex w-full min-w-[500px] flex-col items-center justify-center gap-4">
<AppSegmented {...args} />
<div className="flex items-center gap-2">
<Checkbox
checked={isDirty}
onCheckedChange={(checked) => setIsDirty(checked === 'indeterminate' ? true : checked)}
/>
<p>{isDirty ? 'Dirty' : 'Clean'}</p>
</div>
<PreventNavigation
isDirty={isDirty}
title="Title"
description="Description"
onOk={() => {
alert('ok');
return Promise.resolve();
}}
onCancel={() => {
alert('cancel');
return Promise.resolve();
}}
/>
</div>
);
},
};

View File

@ -1,34 +1,50 @@
'use client';
import * as Tabs from '@radix-ui/react-tabs';
import { Link, useNavigate } from '@tanstack/react-router';
import {
Link,
type LinkProps,
type RegisteredRouter,
type ValidateFromPath,
type ValidateLinkOptions,
} from '@tanstack/react-router';
import { cva } from 'class-variance-authority';
import { motion } from 'framer-motion';
import * as React from 'react';
import { useEffect, useLayoutEffect, useState, useTransition } from 'react';
import { useSize } from '@/hooks/useSize';
import { cn } from '@/lib/classMerge';
import type { OptionsTo } from '@/types/routes';
import { Tooltip } from '../tooltip/Tooltip';
export interface SegmentedItem<T extends string | number = string> {
export interface SegmentedItem<
T extends string | number = string,
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
TFrom extends string = string,
> {
value: T;
label?: React.ReactNode;
icon?: React.ReactNode;
disabled?: boolean;
tooltip?: string;
link?: OptionsTo;
link?: ValidateLinkOptions<TRouter, TOptions, TFrom>;
}
export interface AppSegmentedProps<T extends string | number = string> {
options: SegmentedItem<T>[];
export interface AppSegmentedProps<
T extends string | number = string,
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
TFrom extends string = string,
> {
options: SegmentedItem<T, TRouter, TOptions, TFrom>[];
value?: T;
onChange?: (value: SegmentedItem<T>) => void;
onChange?: (value: SegmentedItem<T, TRouter, TOptions, TFrom>) => void;
className?: string;
size?: 'default' | 'large';
block?: boolean;
type?: 'button' | 'track';
disabled?: boolean;
from?: ValidateFromPath<TRouter, TFrom>;
}
const heightVariants = cva('h-6', {
@ -85,7 +101,7 @@ const triggerVariants = cva(
}
);
const gliderVariants = cva('absolute border-border rounded border', {
const gliderVariants = cva('glider absolute border-border rounded border', {
variants: {
type: {
button: 'bg-item-select',
@ -95,14 +111,26 @@ const gliderVariants = cva('absolute border-border rounded border', {
});
// Create a type for the forwardRef component that includes displayName
type AppSegmentedComponent = (<T extends string = string>(
props: AppSegmentedProps<T> & { ref?: React.ForwardedRef<HTMLDivElement> }
type AppSegmentedComponent = (<
T extends string | number = string,
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
TFrom extends string = string,
>(
props: AppSegmentedProps<T, TRouter, TOptions, TFrom> & {
ref?: React.ForwardedRef<HTMLDivElement>;
}
) => React.ReactElement) & {
displayName?: string;
};
// Update the component definition to properly handle generics
export const AppSegmented: AppSegmentedComponent = (<T extends string | number = string>({
export const AppSegmented: AppSegmentedComponent = (<
T extends string | number = string,
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
TFrom extends string = string,
>({
options,
type = 'track',
value,
@ -110,7 +138,8 @@ export const AppSegmented: AppSegmentedComponent = (<T extends string | number =
className,
size = 'default',
block = false,
}: AppSegmentedProps<T>) => {
from,
}: AppSegmentedProps<T, TRouter, TOptions, TFrom>) => {
const rootRef = React.useRef<HTMLDivElement>(null);
const elementSize = useSize(rootRef, 25);
const tabRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
@ -120,7 +149,7 @@ export const AppSegmented: AppSegmentedComponent = (<T extends string | number =
transform: 'translateX(0)',
});
const [isMeasured, setIsMeasured] = useState(false);
const [isPending, startTransition] = useTransition();
const [_isPending, startTransition] = useTransition();
const handleTabClick = (value: string) => {
const item = options.find((item) => item.value === value);
@ -163,7 +192,6 @@ export const AppSegmented: AppSegmentedComponent = (<T extends string | number =
<Tabs.Root
ref={rootRef}
value={selectedValue as string}
// onValueChange={handleTabClick}
className={cn(segmentedVariants({ block, type }), heightVariants({ size }), className)}
>
{isMeasured && (
@ -197,12 +225,13 @@ export const AppSegmented: AppSegmentedComponent = (<T extends string | number =
{options.map((item) => (
<SegmentedTrigger
key={item.value}
item={item as SegmentedItem<string>}
item={item as SegmentedItem<string, TRouter, TOptions, TFrom>}
selectedValue={selectedValue as string}
size={size}
block={block}
tabRefs={tabRefs}
handleTabClick={handleTabClick}
from={from}
/>
))}
</Tabs.List>
@ -212,34 +241,58 @@ export const AppSegmented: AppSegmentedComponent = (<T extends string | number =
AppSegmented.displayName = 'AppSegmented';
interface SegmentedTriggerProps<T extends string = string> {
item: SegmentedItem<T>;
interface SegmentedTriggerProps<
T extends string = string,
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
TFrom extends string = string,
> {
item: SegmentedItem<T, TRouter, TOptions, TFrom>;
selectedValue: string;
size: AppSegmentedProps<T>['size'];
block: AppSegmentedProps<T>['block'];
size: AppSegmentedProps<T, TRouter, TOptions, TFrom>['size'];
block: AppSegmentedProps<T, TRouter, TOptions, TFrom>['block'];
tabRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
handleTabClick: (value: string) => void;
from?: ValidateFromPath<TRouter, TFrom>;
}
function SegmentedTriggerComponent<T extends string = string>(props: SegmentedTriggerProps<T>) {
const { item, selectedValue, size, block, tabRefs, handleTabClick } = props;
function SegmentedTriggerComponent<
T extends string = string,
TRouter extends RegisteredRouter = RegisteredRouter,
TOptions = unknown,
TFrom extends string = string,
>(props: SegmentedTriggerProps<T, TRouter, TOptions, TFrom>) {
const { item, selectedValue, size, block, tabRefs, handleTabClick, from } = props;
const { tooltip, label, icon, disabled, value, link } = item;
const navigate = useNavigate();
const LinkDiv = link ? Link : 'div';
const handleClick = async (e: React.MouseEvent) => {
if (link) {
e.preventDefault();
handleTabClick(value);
// Wait for a short duration to allow the animation to complete
await new Promise((resolve) => setTimeout(resolve, 1));
navigate(link);
} else {
handleTabClick(value);
}
const handleClick = (_e: React.MouseEvent) => {
handleTabClick(value);
};
const commonProps = {
onClick: handleClick,
'data-testid': `segmented-trigger-${value}`,
};
const linkContent = (
<>
{icon && <span className={cn('flex items-center text-sm')}>{icon}</span>}
{label && <span className={cn('text-sm')}>{label}</span>}
</>
);
const linkDiv = link ? (
<Link
{...(link as LinkProps)}
from={(from === './' ? undefined : from) as '/'}
{...commonProps}
>
{linkContent}
</Link>
) : (
<div {...commonProps}>{linkContent}</div>
);
return (
<Tooltip title={tooltip || ''} sideOffset={10} delayDuration={150}>
<Tabs.Trigger
@ -259,10 +312,7 @@ function SegmentedTriggerComponent<T extends string = string>(props: SegmentedTr
})
)}
>
<LinkDiv {...link} onClick={handleClick} data-testid={`segmented-trigger-${value}`}>
{icon && <span className={cn('flex items-center text-sm')}>{icon}</span>}
{label && <span className={cn('text-sm')}>{label}</span>}
</LinkDiv>
{linkDiv}
</Tabs.Trigger>
</Tooltip>
);

View File

@ -0,0 +1,78 @@
import { type ParsedLocation, useLocation } from '@tanstack/react-router';
import React, { useCallback, useMemo } from 'react';
import { AppSegmented } from '@/components/ui/segmented';
import { useIsMetricReadOnly } from '@/context/Metrics/useIsMetricReadOnly';
interface MetricContainerHeaderSegmentProps {
metricId: string;
metric_version_number: number | undefined;
}
export const MetricContainerHeaderSegment: React.FC<MetricContainerHeaderSegmentProps> = React.memo(
(props) => {
const { metricId, metric_version_number } = props;
const { isFetched, isError } = useIsMetricReadOnly({
metricId,
});
if (!isFetched || isError) return null;
return <MetricSegments {...props} />;
}
);
MetricContainerHeaderSegment.displayName = 'MetricContainerHeaderSegment';
type MetricView = 'chart' | 'results' | 'sql';
const MetricSegments: React.FC<MetricContainerHeaderSegmentProps> = React.memo(() => {
const location = useLocation({
select: useCallback((location: ParsedLocation) => {
// Get the last value after a slash in the pathname
const pathSegments = location.pathname.split('/').filter(Boolean);
const lastSegment = pathSegments[pathSegments.length - 1] || '';
return lastSegment;
}, []),
});
const selectedView: MetricView = useMemo(() => {
if (location === 'chart') return 'chart';
if (location === 'results') return 'results';
if (location === 'sql') return 'sql';
return 'chart';
}, [location]);
return (
<AppSegmented
type="button"
from={'./' as unknown as '/app/metrics/$metricId'}
options={[
{
label: 'Chart',
value: 'chart' satisfies MetricView,
link: {
to: './chart',
},
},
{
label: 'Results',
value: 'results' satisfies MetricView,
link: {
to: './results',
},
},
{
label: 'SQL',
value: 'sql' satisfies MetricView,
link: {
to: './sql',
},
},
]}
value={selectedView}
/>
);
});
MetricSegments.displayName = 'MetricSegments';

View File

@ -50,7 +50,7 @@ import { Route as AppAppAssetDashboardsDashboardIdIndexRouteImport } from './rou
import { Route as AppAppAssetCollectionsCollectionIdIndexRouteImport } from './routes/app/_app/_asset/collections.$collectionId.index'
import { Route as AppAppAssetChatsChatIdIndexRouteImport } from './routes/app/_app/_asset/chats.$chatId.index'
import { Route as AppAppAssetMetricsMetricIdSqlRouteImport } from './routes/app/_app/_asset/metrics.$metricId.sql'
import { Route as AppAppAssetMetricsMetricIdResultRouteImport } from './routes/app/_app/_asset/metrics.$metricId.result'
import { Route as AppAppAssetMetricsMetricIdResultsRouteImport } from './routes/app/_app/_asset/metrics.$metricId.results'
import { Route as AppAppAssetMetricsMetricIdChartRouteImport } from './routes/app/_app/_asset/metrics.$metricId.chart'
import { Route as AppAppAssetReportsReportIdMetricsMetricIdRouteImport } from './routes/app/_app/_asset/reports.$reportId.metrics.$metricId'
import { Route as AppAppAssetDashboardsDashboardIdMetricsMetricIdRouteImport } from './routes/app/_app/_asset/dashboards.$dashboardId.metrics.$metricId'
@ -286,10 +286,10 @@ const AppAppAssetMetricsMetricIdSqlRoute =
path: '/sql',
getParentRoute: () => AppAppAssetMetricsMetricIdRoute,
} as any)
const AppAppAssetMetricsMetricIdResultRoute =
AppAppAssetMetricsMetricIdResultRouteImport.update({
id: '/result',
path: '/result',
const AppAppAssetMetricsMetricIdResultsRoute =
AppAppAssetMetricsMetricIdResultsRouteImport.update({
id: '/results',
path: '/results',
getParentRoute: () => AppAppAssetMetricsMetricIdRoute,
} as any)
const AppAppAssetMetricsMetricIdChartRoute =
@ -464,7 +464,7 @@ export interface FileRoutesByFullPath {
'/app/settings/datasources/add': typeof AppSettingsSettingsDatasourcesAddRoute
'/app/settings/datasources/': typeof AppSettingsSettingsDatasourcesIndexRoute
'/app/metrics/$metricId/chart': typeof AppAppAssetMetricsMetricIdChartRoute
'/app/metrics/$metricId/result': typeof AppAppAssetMetricsMetricIdResultRoute
'/app/metrics/$metricId/results': typeof AppAppAssetMetricsMetricIdResultsRoute
'/app/metrics/$metricId/sql': typeof AppAppAssetMetricsMetricIdSqlRoute
'/app/chats/$chatId': typeof AppAppAssetChatsChatIdIndexRoute
'/app/collections/$collectionId': typeof AppAppAssetCollectionsCollectionIdIndexRoute
@ -522,7 +522,7 @@ export interface FileRoutesByTo {
'/app/settings/datasources/add': typeof AppSettingsSettingsDatasourcesAddRoute
'/app/settings/datasources': typeof AppSettingsSettingsDatasourcesIndexRoute
'/app/metrics/$metricId/chart': typeof AppAppAssetMetricsMetricIdChartRoute
'/app/metrics/$metricId/result': typeof AppAppAssetMetricsMetricIdResultRoute
'/app/metrics/$metricId/results': typeof AppAppAssetMetricsMetricIdResultsRoute
'/app/metrics/$metricId/sql': typeof AppAppAssetMetricsMetricIdSqlRoute
'/app/chats/$chatId': typeof AppAppAssetChatsChatIdIndexRoute
'/app/collections/$collectionId': typeof AppAppAssetCollectionsCollectionIdIndexRoute
@ -585,7 +585,7 @@ export interface FileRoutesById {
'/app/_settings/settings/datasources/add': typeof AppSettingsSettingsDatasourcesAddRoute
'/app/_settings/settings/datasources/': typeof AppSettingsSettingsDatasourcesIndexRoute
'/app/_app/_asset/metrics/$metricId/chart': typeof AppAppAssetMetricsMetricIdChartRoute
'/app/_app/_asset/metrics/$metricId/result': typeof AppAppAssetMetricsMetricIdResultRoute
'/app/_app/_asset/metrics/$metricId/results': typeof AppAppAssetMetricsMetricIdResultsRoute
'/app/_app/_asset/metrics/$metricId/sql': typeof AppAppAssetMetricsMetricIdSqlRoute
'/app/_app/_asset/chats/$chatId/': typeof AppAppAssetChatsChatIdIndexRoute
'/app/_app/_asset/collections/$collectionId/': typeof AppAppAssetCollectionsCollectionIdIndexRoute
@ -646,7 +646,7 @@ export interface FileRouteTypes {
| '/app/settings/datasources/add'
| '/app/settings/datasources/'
| '/app/metrics/$metricId/chart'
| '/app/metrics/$metricId/result'
| '/app/metrics/$metricId/results'
| '/app/metrics/$metricId/sql'
| '/app/chats/$chatId'
| '/app/collections/$collectionId'
@ -704,7 +704,7 @@ export interface FileRouteTypes {
| '/app/settings/datasources/add'
| '/app/settings/datasources'
| '/app/metrics/$metricId/chart'
| '/app/metrics/$metricId/result'
| '/app/metrics/$metricId/results'
| '/app/metrics/$metricId/sql'
| '/app/chats/$chatId'
| '/app/collections/$collectionId'
@ -766,7 +766,7 @@ export interface FileRouteTypes {
| '/app/_settings/settings/datasources/add'
| '/app/_settings/settings/datasources/'
| '/app/_app/_asset/metrics/$metricId/chart'
| '/app/_app/_asset/metrics/$metricId/result'
| '/app/_app/_asset/metrics/$metricId/results'
| '/app/_app/_asset/metrics/$metricId/sql'
| '/app/_app/_asset/chats/$chatId/'
| '/app/_app/_asset/collections/$collectionId/'
@ -1098,11 +1098,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppAppAssetMetricsMetricIdSqlRouteImport
parentRoute: typeof AppAppAssetMetricsMetricIdRoute
}
'/app/_app/_asset/metrics/$metricId/result': {
id: '/app/_app/_asset/metrics/$metricId/result'
path: '/result'
fullPath: '/app/metrics/$metricId/result'
preLoaderRoute: typeof AppAppAssetMetricsMetricIdResultRouteImport
'/app/_app/_asset/metrics/$metricId/results': {
id: '/app/_app/_asset/metrics/$metricId/results'
path: '/results'
fullPath: '/app/metrics/$metricId/results'
preLoaderRoute: typeof AppAppAssetMetricsMetricIdResultsRouteImport
parentRoute: typeof AppAppAssetMetricsMetricIdRoute
}
'/app/_app/_asset/metrics/$metricId/chart': {
@ -1261,15 +1261,15 @@ declare module '@tanstack/react-start/server' {
interface AppAppAssetMetricsMetricIdRouteChildren {
AppAppAssetMetricsMetricIdChartRoute: typeof AppAppAssetMetricsMetricIdChartRoute
AppAppAssetMetricsMetricIdResultRoute: typeof AppAppAssetMetricsMetricIdResultRoute
AppAppAssetMetricsMetricIdResultsRoute: typeof AppAppAssetMetricsMetricIdResultsRoute
AppAppAssetMetricsMetricIdSqlRoute: typeof AppAppAssetMetricsMetricIdSqlRoute
}
const AppAppAssetMetricsMetricIdRouteChildren: AppAppAssetMetricsMetricIdRouteChildren =
{
AppAppAssetMetricsMetricIdChartRoute: AppAppAssetMetricsMetricIdChartRoute,
AppAppAssetMetricsMetricIdResultRoute:
AppAppAssetMetricsMetricIdResultRoute,
AppAppAssetMetricsMetricIdResultsRoute:
AppAppAssetMetricsMetricIdResultsRoute,
AppAppAssetMetricsMetricIdSqlRoute: AppAppAssetMetricsMetricIdSqlRoute,
}

View File

@ -1,6 +0,0 @@
import { createFileRoute } from '@tanstack/react-router';
import * as metricChartServerAssetContext from '@/context/BusterAssets/metric-server/metricChartServerAssetContext';
export const Route = createFileRoute('/app/_app/_asset/metrics/$metricId/result')({
...metricChartServerAssetContext,
});

View File

@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router';
import * as metricResultsServerAssetContext from '@/context/BusterAssets/metric-server/metricResultsServerAssetContext';
export const Route = createFileRoute('/app/_app/_asset/metrics/$metricId/results')({
...metricResultsServerAssetContext,
});

View File

@ -9,10 +9,7 @@ import { create } from 'mutative';
import { useMemoizedFn } from '@/hooks';
import { queryKeys } from '@/api/query_keys';
import type { RustApiError } from '../errors';
import type {
GetReportIndividualResponse,
UpdateReportResponse
} from '@buster/server-shared/reports';
import type { GetReportResponse, UpdateReportResponse } from '@buster/server-shared/reports';
import {
getReportsList,
getReportsList_server,
@ -86,12 +83,9 @@ export const prefetchGetReportsListClient = async (
/**
* Hook to get an individual report by ID
*/
export const useGetReport = <T = GetReportIndividualResponse>(
export const useGetReport = <T = GetReportResponse>(
{ reportId, versionNumber }: { reportId: string | undefined; versionNumber?: number },
options?: Omit<
UseQueryOptions<GetReportIndividualResponse, RustApiError, T>,
'queryKey' | 'queryFn'
>
options?: Omit<UseQueryOptions<GetReportResponse, RustApiError, T>, 'queryKey' | 'queryFn'>
) => {
const queryFn = useMemoizedFn(() => {
return getReportById(reportId!);
@ -127,7 +121,7 @@ export const useUpdateReport = () => {
UpdateReportResponse,
RustApiError,
Parameters<typeof updateReport>[0],
{ previousReport?: GetReportIndividualResponse }
{ previousReport?: GetReportResponse }
>({
mutationFn: updateReport,
onMutate: async ({ reportId, ...data }) => {
@ -137,7 +131,7 @@ export const useUpdateReport = () => {
});
// Snapshot the previous value
const previousReport = queryClient.getQueryData<GetReportIndividualResponse>(
const previousReport = queryClient.getQueryData<GetReportResponse>(
queryKeys.reportsGetReport(reportId).queryKey
);

View File

@ -4,7 +4,7 @@ import { BASE_URL_V2 } from '../config';
import type {
GetReportsListRequest,
GetReportsListResponse,
GetReportIndividualResponse,
GetReportResponse,
UpdateReportRequest,
UpdateReportResponse
} from '@buster/server-shared/reports';
@ -34,14 +34,14 @@ export const getReportsList_server = async (params?: Parameters<typeof getReport
* Get an individual report by ID
*/
export const getReportById = async (reportId: string) => {
return mainApiV2.get<GetReportIndividualResponse>(`/reports/${reportId}`).then((res) => res.data);
return mainApiV2.get<GetReportResponse>(`/reports/${reportId}`).then((res) => res.data);
};
/**
* Server-side version of getReportById
*/
export const getReportById_server = async (reportId: string) => {
return await serverFetch<GetReportIndividualResponse>(`/reports/${reportId}`, {
return await serverFetch<GetReportResponse>(`/reports/${reportId}`, {
baseURL: BASE_URL_V2,
method: 'GET'
});

View File

@ -1,7 +1,7 @@
import { queryOptions } from '@tanstack/react-query';
import type {
GetReportsListResponse,
GetReportIndividualResponse,
GetReportResponse,
GetReportsListRequest
} from '@buster/server-shared/reports';
@ -14,7 +14,7 @@ const reportsGetList = (filters?: GetReportsListRequest) =>
});
const reportsGetReport = (reportId: string, versionNumber?: number | null) =>
queryOptions<GetReportIndividualResponse>({
queryOptions<GetReportResponse>({
queryKey: ['reports', 'get', reportId, versionNumber || 'INITIAL'] as const,
staleTime: 60 * 1000 // 60 seconds
});