mirror of https://github.com/buster-so/buster.git
list type safety
This commit is contained in:
parent
9f26eb9412
commit
ad0231f031
|
@ -37,7 +37,7 @@ import {
|
|||
Trash,
|
||||
} from '@/components/ui/icons';
|
||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { useIsChatMode } from '@/context/Chats/useIsChatMode';
|
||||
import { useIsChatMode } from '@/context/Chats/useMode';
|
||||
import { useDownloadMetricDataCSV } from '@/context/Metrics/useDownloadMetricDataCSV';
|
||||
import { useDownloadPNGSelectMenu } from '@/context/Metrics/useDownloadMetricDataPNG';
|
||||
import { useRenameMetricOnPage } from '@/context/Metrics/useRenameMetricOnPage';
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import type { RegisteredRouter } from '@tanstack/react-router';
|
||||
import { create } from 'lodash';
|
||||
import type { BusterListRowItem } from './interfaces';
|
||||
|
||||
export function createLinkItems<
|
||||
T,
|
||||
TRouter extends RegisteredRouter,
|
||||
TOptions,
|
||||
TFrom extends string = string,
|
||||
>(
|
||||
items: BusterListRowItem<T, TRouter, TOptions, TFrom>[]
|
||||
): BusterListRowItem<T, TRouter, TOptions, TFrom>[] {
|
||||
return items;
|
||||
}
|
||||
|
||||
export function createLinkItem<
|
||||
T,
|
||||
TRouter extends RegisteredRouter = RegisteredRouter,
|
||||
TOptions = Record<string, unknown>,
|
||||
TFrom extends string = string,
|
||||
>(
|
||||
item: BusterListRowItem<T, TRouter, TOptions, TFrom>
|
||||
): BusterListRowItem<T, TRouter, TOptions, TFrom> {
|
||||
return item;
|
||||
}
|
||||
|
||||
const test = createLinkItem({
|
||||
id: '1',
|
||||
data: {
|
||||
name: 'Test',
|
||||
},
|
||||
link: {
|
||||
to: '/app/metrics/$metricId',
|
||||
params: {
|
||||
metricId: '1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const test2 = createLinkItem<{
|
||||
swag: boolean;
|
||||
}>({
|
||||
id: '1',
|
||||
data: {
|
||||
swag: true,
|
||||
},
|
||||
link: {
|
||||
to: '/app/metrics/$metricId',
|
||||
params: {
|
||||
metricId: '1',
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
export * from './create-link-items';
|
||||
export * from './interfaces';
|
||||
export * from './ListSelectedOptionPopup';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { LinkProps } from '@tanstack/react-router';
|
||||
import type { LinkProps, RegisteredRouter } from '@tanstack/react-router';
|
||||
import type React from 'react';
|
||||
import type { OptionsTo } from '@/types/routes';
|
||||
import type { ILinkProps } from '@/types/routes';
|
||||
import type { ContextMenuProps } from '../../context-menu/ContextMenu';
|
||||
|
||||
export interface BusterListProps<T = unknown> {
|
||||
|
@ -31,8 +31,12 @@ export type BusterListColumn<T = unknown> = {
|
|||
};
|
||||
}[keyof T];
|
||||
|
||||
type BusterListRowLink = {
|
||||
link: OptionsTo;
|
||||
type BusterListRowLink<
|
||||
TRouter extends RegisteredRouter = RegisteredRouter,
|
||||
TOptions = Record<string, unknown>,
|
||||
TFrom extends string = string,
|
||||
> = {
|
||||
link: ILinkProps<TRouter, TOptions, TFrom>;
|
||||
preloadDelay?: LinkProps['preloadDelay'];
|
||||
preload?: LinkProps['preload'];
|
||||
};
|
||||
|
@ -41,7 +45,12 @@ type BusterListRowNotLink = {
|
|||
link?: never;
|
||||
};
|
||||
|
||||
export type BusterListRowItem<T = unknown> = {
|
||||
export type BusterListRowItem<
|
||||
T = unknown,
|
||||
TRouter extends RegisteredRouter = RegisteredRouter,
|
||||
TOptions = Record<string, unknown>,
|
||||
TFrom extends string = string,
|
||||
> = {
|
||||
id: string;
|
||||
data: T | null;
|
||||
onClick?: () => void;
|
||||
|
@ -49,7 +58,7 @@ export type BusterListRowItem<T = unknown> = {
|
|||
rowSection?: BusterListSectionRow;
|
||||
hidden?: boolean;
|
||||
dataTestId?: string;
|
||||
} & (BusterListRowLink | BusterListRowNotLink);
|
||||
} & (BusterListRowLink<TRouter, TOptions, TFrom> | BusterListRowNotLink);
|
||||
|
||||
export interface BusterListSectionRow {
|
||||
title: string;
|
||||
|
|
|
@ -8,3 +8,11 @@ export const useIsChatMode = () => {
|
|||
});
|
||||
return chatId !== undefined;
|
||||
};
|
||||
|
||||
export const useIsFileMode = () => {
|
||||
const chatId = useParams({
|
||||
select: stableSelect,
|
||||
strict: false,
|
||||
});
|
||||
return chatId === undefined;
|
||||
};
|
|
@ -8,13 +8,12 @@ import {
|
|||
} from '@/components/features/metrics/StatusBadgeIndicator';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import type { BusterListColumn, BusterListRowItem } from '@/components/ui/list';
|
||||
import { BusterList, ListEmptyStateWithButton } from '@/components/ui/list';
|
||||
import { BusterList, createLinkItem, ListEmptyStateWithButton } from '@/components/ui/list';
|
||||
import { useCreateListByDate } from '@/components/ui/list/useCreateListByDate';
|
||||
import { Text } from '@/components/ui/typography';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { formatDate } from '@/lib/date';
|
||||
import { makeHumanReadble } from '@/lib/text';
|
||||
import type { OptionsTo } from '@/types/routes';
|
||||
import { MetricSelectedOptionPopup } from './MetricItemsSelectedPopup';
|
||||
|
||||
export const MetricItemsContainer: React.FC<{
|
||||
|
@ -36,16 +35,18 @@ export const MetricItemsContainer: React.FC<{
|
|||
const metricsByDate: BusterListRowItem<BusterMetricListItem>[] = useMemo(() => {
|
||||
return Object.entries(logsRecord).flatMap<BusterListRowItem<BusterMetricListItem>>(
|
||||
([key, metrics]) => {
|
||||
const records = metrics.map((metric) => ({
|
||||
id: metric.id,
|
||||
data: metric,
|
||||
link: {
|
||||
to: '/app/metrics/$metricId',
|
||||
params: {
|
||||
metricId: metric.id,
|
||||
const records = metrics.map((metric) =>
|
||||
createLinkItem({
|
||||
id: metric.id,
|
||||
data: metric,
|
||||
link: {
|
||||
to: '/app/metrics/$metricId',
|
||||
params: {
|
||||
metricId: metric.id,
|
||||
},
|
||||
},
|
||||
} satisfies OptionsTo,
|
||||
}));
|
||||
})
|
||||
);
|
||||
const hasRecords = records.length > 0;
|
||||
if (!hasRecords) {
|
||||
return [];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Link } from '@tanstack/react-router';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { useGetMetric } from '@/api/buster_rest/metrics';
|
||||
import { CreateChatButton } from '@/components/features/AssetLayout/CreateChatButton';
|
||||
import { SaveMetricToCollectionButton } from '@/components/features/buttons/SaveMetricToCollectionButton';
|
||||
|
@ -7,8 +7,8 @@ import { SaveMetricToDashboardButton } from '@/components/features/buttons/SaveM
|
|||
import { ShareMetricButton } from '@/components/features/buttons/ShareMetricButton';
|
||||
import { ThreeDotMenuButton } from '@/components/features/metrics/MetricThreeDotMenu';
|
||||
import { SquareChartPen } from '@/components/ui/icons';
|
||||
import { useIsFileMode } from '@/context/Chats/useMode';
|
||||
import { useIsMetricReadOnly } from '@/context/Metrics/useIsMetricReadOnly';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { canEdit, getIsEffectiveOwner } from '@/lib/share';
|
||||
import { FileButtonContainer } from '../FileButtonContainer';
|
||||
import { HideButtonContainer } from '../HideButtonContainer';
|
||||
|
@ -18,6 +18,7 @@ export const MetricContainerHeaderButtons: React.FC<{
|
|||
metricId: string;
|
||||
metricVersionNumber: number;
|
||||
}> = React.memo(({ metricId, metricVersionNumber }) => {
|
||||
const isFileMode = useIsFileMode();
|
||||
const { isViewingOldVersion } = useIsMetricReadOnly({
|
||||
metricId: metricId || '',
|
||||
});
|
||||
|
@ -43,7 +44,7 @@ export const MetricContainerHeaderButtons: React.FC<{
|
|||
isViewingOldVersion={isViewingOldVersion}
|
||||
versionNumber={metricVersionNumber}
|
||||
/>
|
||||
<HideButtonContainer show={selectedLayout === 'file-only'}>
|
||||
<HideButtonContainer show={isFileMode}>
|
||||
<CreateChatButton assetId={metricId} assetType="metric" />
|
||||
</HideButtonContainer>
|
||||
</FileButtonContainer>
|
||||
|
@ -53,62 +54,49 @@ export const MetricContainerHeaderButtons: React.FC<{
|
|||
MetricContainerHeaderButtons.displayName = 'MetricContainerHeaderButtons';
|
||||
|
||||
const EditChartButton = React.memo(({ metricId }: { metricId: string }) => {
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const selectedFileViewSecondary = useChatLayoutContextSelector(
|
||||
(x) => x.selectedFileViewSecondary
|
||||
);
|
||||
const chatId = useChatIndividualContextSelector((x) => x.chatId);
|
||||
const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber);
|
||||
const editableSecondaryView: MetricFileViewSecondary = 'chart-edit';
|
||||
const isSelectedView = selectedFileViewSecondary === editableSecondaryView;
|
||||
const isEditorOpen = true;
|
||||
// const metricVersionNumber = useChatLayoutContextSelector((x) => x.metricVersionNumber);
|
||||
// const editableSecondaryView: MetricFileViewSecondary = 'chart-edit';
|
||||
// const isSelectedView = selectedFileViewSecondary === editableSecondaryView;
|
||||
|
||||
const href = useMemo(() => {
|
||||
if (isSelectedView) {
|
||||
return assetParamsToRoute({
|
||||
chatId,
|
||||
assetId: metricId,
|
||||
type: 'metric',
|
||||
secondaryView: undefined,
|
||||
versionNumber: metricVersionNumber,
|
||||
page: 'chart',
|
||||
});
|
||||
}
|
||||
// const href = useMemo(() => {
|
||||
// if (isSelectedView) {
|
||||
// return assetParamsToRoute({
|
||||
// chatId,
|
||||
// assetId: metricId,
|
||||
// type: 'metric',
|
||||
// secondaryView: undefined,
|
||||
// versionNumber: metricVersionNumber,
|
||||
// page: 'chart',
|
||||
// });
|
||||
// }
|
||||
|
||||
return assetParamsToRoute({
|
||||
chatId,
|
||||
assetId: metricId,
|
||||
type: 'metric',
|
||||
secondaryView: 'chart-edit',
|
||||
versionNumber: metricVersionNumber,
|
||||
page: 'chart',
|
||||
});
|
||||
}, [chatId, metricId, isSelectedView, metricVersionNumber]);
|
||||
|
||||
//I HAVE NO IDEA WHY... but onClickButton is called twice if wrapped in a link
|
||||
const onClickButton = useMemoizedFn(() => {
|
||||
onChangePage(href, { shallow: true });
|
||||
});
|
||||
// return assetParamsToRoute({
|
||||
// chatId,
|
||||
// assetId: metricId,
|
||||
// type: 'metric',
|
||||
// secondaryView: 'chart-edit',
|
||||
// versionNumber: metricVersionNumber,
|
||||
// page: 'chart',
|
||||
// });
|
||||
// }, [chatId, metricId, isSelectedView, metricVersionNumber]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={true}
|
||||
passHref
|
||||
to="/app/metrics/$metricId/chart"
|
||||
params={{
|
||||
metricId,
|
||||
}}
|
||||
data-testid="edit-chart-button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClickButton();
|
||||
}}
|
||||
>
|
||||
<SelectableButton
|
||||
tooltipText="Edit chart"
|
||||
icon={<SquareChartPen />}
|
||||
selected={isSelectedView}
|
||||
selected={isEditorOpen}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { FileRouteTypes } from '@/routeTree.gen';
|
||||
import type { OptionsTo } from '@/types/routes';
|
||||
import type { ILinkProps } from '@/types/routes';
|
||||
|
||||
type RouteFilePaths = FileRouteTypes['to'];
|
||||
|
||||
|
@ -269,7 +269,7 @@ class RouteBuilder<T extends RouteBuilderState = NonNullable<unknown>> {
|
|||
/**
|
||||
* Build navigation options with route and params
|
||||
*/
|
||||
buildNavigationOptions(): OptionsTo {
|
||||
buildNavigationOptions(): ILinkProps {
|
||||
const route = this.build();
|
||||
const params = this.getParams();
|
||||
const search = this.getSearchParams();
|
||||
|
@ -289,7 +289,7 @@ class RouteBuilder<T extends RouteBuilderState = NonNullable<unknown>> {
|
|||
}
|
||||
|
||||
// Type assertion through unknown for complex generic type
|
||||
return navOptions as OptionsTo;
|
||||
return navOptions as unknown as ILinkProps;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -425,7 +425,7 @@ class RouteBuilder<T extends RouteBuilderState = NonNullable<unknown>> {
|
|||
* });
|
||||
* // Result: { to: '/app/dashboards/dashboard-456', params: { dashboardId: 'dashboard-456', metricId: 'metric-789' }, search: { dashboard_version_number: 3, metric_version_number: 2 } }
|
||||
*/
|
||||
export const assetParamsToRoute = (params: AssetParamsToRoute): OptionsTo => {
|
||||
export const assetParamsToRoute = (params: AssetParamsToRoute): ILinkProps => {
|
||||
const builder = new RouteBuilder();
|
||||
|
||||
// Build route based on asset type and additional params
|
||||
|
|
|
@ -14,7 +14,7 @@ export type ILinkOptions = Partial<
|
|||
>;
|
||||
|
||||
export type ILinkProps<
|
||||
TRouter extends RegisteredRouter,
|
||||
TOptions,
|
||||
TRouter extends RegisteredRouter = RegisteredRouter,
|
||||
TOptions = Record<string, unknown>,
|
||||
TFrom extends string = string,
|
||||
> = ValidateLinkOptions<TRouter, TOptions, TFrom> & ILinkOptions;
|
||||
|
|
Loading…
Reference in New Issue