mirror of https://github.com/buster-so/buster.git
set up hydration boundary for metric
This commit is contained in:
parent
a810e6261a
commit
8609748338
|
@ -35,6 +35,7 @@
|
|||
"@tanstack/react-query": "^5.71.5",
|
||||
"@tanstack/react-query-devtools": "^5.71.5",
|
||||
"@tanstack/react-query-persist-client": "^5.71.5",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/prettier": "^2.7.3",
|
||||
"@types/react-color": "^3.0.13",
|
||||
|
@ -6960,6 +6961,26 @@
|
|||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-table": {
|
||||
"version": "8.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz",
|
||||
"integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/table-core": "8.21.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/store": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.0.tgz",
|
||||
|
@ -6970,6 +6991,19 @@
|
|||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/table-core": {
|
||||
"version": "8.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz",
|
||||
"integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"@tanstack/react-query": "^5.71.5",
|
||||
"@tanstack/react-query-devtools": "^5.71.5",
|
||||
"@tanstack/react-query-persist-client": "^5.71.5",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/prettier": "^2.7.3",
|
||||
"@types/react-color": "^3.0.13",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { useDebounceFn, useMemoizedFn } from '@/hooks';
|
||||
import { useDebounceFn, useMemoizedFn, useWhyDidYouUpdate } from '@/hooks';
|
||||
import {
|
||||
deleteMetrics,
|
||||
duplicateMetric,
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
import { prepareMetricUpdateMetric, upgradeMetricToIMetric } from '@/lib/metrics';
|
||||
import { metricsQueryKeys } from '@/api/query_keys/metric';
|
||||
import { collectionQueryKeys } from '@/api/query_keys/collection';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useBusterAssetsContextSelector } from '@/context/Assets/BusterAssetsProvider';
|
||||
import { useGetUserFavorites } from '../users';
|
||||
import type { IBusterMetric } from '@/api/asset_interfaces/metric';
|
||||
|
@ -42,7 +42,8 @@ export const useGetMetric = <TData = IBusterMetric>(
|
|||
},
|
||||
select?: (data: IBusterMetric) => TData
|
||||
) => {
|
||||
const { setOriginalMetric } = useOriginalMetricStore();
|
||||
const setOriginalMetric = useOriginalMetricStore((x) => x.setOriginalMetric);
|
||||
const getOriginalMetric = useOriginalMetricStore((x) => x.getOriginalMetric);
|
||||
const searchParams = useSearchParams();
|
||||
const queryVersionNumber = searchParams.get('metric_version_number');
|
||||
const getAssetPassword = useBusterAssetsContextSelector((x) => x.getAssetPassword);
|
||||
|
@ -68,7 +69,7 @@ export const useGetMetric = <TData = IBusterMetric>(
|
|||
return updatedMetric;
|
||||
});
|
||||
|
||||
return useQuery({
|
||||
const result = useQuery({
|
||||
...options,
|
||||
queryFn,
|
||||
select,
|
||||
|
@ -80,6 +81,8 @@ export const useGetMetric = <TData = IBusterMetric>(
|
|||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const useGetMetricsList = (
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { prefetchGetMetric } from '@/api/buster_rest/metrics';
|
||||
import { metricsQueryKeys } from '@/api/query_keys/metric';
|
||||
import { HydrationBoundaryMetricStore } from '@/context/Metrics/useOriginalMetricStore';
|
||||
import { AppAssetCheckLayout } from '@/layouts/AppAssetCheckLayout';
|
||||
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
|
||||
|
||||
|
@ -12,13 +14,16 @@ export default async function MetricLayout({
|
|||
const { metricId } = await params;
|
||||
const queryClient = await prefetchGetMetric({ id: metricId });
|
||||
|
||||
const metric = queryClient.getQueryData(metricsQueryKeys.metricsGetMetric(metricId).queryKey);
|
||||
// const state = queryClient.getQueryState(queryKeys.metricsGetMetric(metricId).queryKey);
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<AppAssetCheckLayout assetId={metricId} type="metric">
|
||||
{children}
|
||||
</AppAssetCheckLayout>
|
||||
<HydrationBoundaryMetricStore metric={metric}>
|
||||
<AppAssetCheckLayout assetId={metricId} type="metric">
|
||||
{children}
|
||||
</AppAssetCheckLayout>
|
||||
</HydrationBoundaryMetricStore>
|
||||
</HydrationBoundary>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
import { renderHook } from '@testing-library/react';
|
||||
import { useAxisTitles } from './useAxisTitles';
|
||||
import { useXAxisTitle } from './useXAxisTitle';
|
||||
import { useYAxisTitle } from './useYAxisTitle';
|
||||
import { useY2AxisTitle } from './useY2AxisTitle';
|
||||
import { ChartType } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { ChartEncodes, ComboChartAxis } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { IColumnLabelFormat } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { SimplifiedColumnType } from '@/api/asset_interfaces/metric';
|
||||
|
||||
// Mock the dependency hooks
|
||||
jest.mock('./useXAxisTitle', () => ({
|
||||
useXAxisTitle: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('./useYAxisTitle', () => ({
|
||||
useYAxisTitle: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('./useY2AxisTitle', () => ({
|
||||
useY2AxisTitle: jest.fn()
|
||||
}));
|
||||
|
||||
describe('useAxisTitles', () => {
|
||||
const defaultProps = {
|
||||
selectedAxis: {
|
||||
x: ['date'],
|
||||
y: ['value'],
|
||||
y2: ['secondValue']
|
||||
} as ComboChartAxis,
|
||||
columnLabelFormats: {
|
||||
date: {
|
||||
columnType: 'date' as SimplifiedColumnType,
|
||||
style: 'date' as const
|
||||
},
|
||||
value: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
},
|
||||
secondValue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
} as Record<string, IColumnLabelFormat>,
|
||||
yAxisShowAxisTitle: true,
|
||||
yAxisAxisTitle: 'Value Axis',
|
||||
xAxisShowAxisTitle: true,
|
||||
xAxisAxisTitle: 'Date Axis',
|
||||
selectedChartType: ChartType.Line,
|
||||
y2AxisShowAxisTitle: true,
|
||||
y2AxisAxisTitle: 'Second Value Axis',
|
||||
barLayout: 'vertical' as const
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Setup default mock returns
|
||||
(useXAxisTitle as jest.Mock).mockReturnValue('X Axis Title');
|
||||
(useYAxisTitle as jest.Mock).mockReturnValue('Y Axis Title');
|
||||
(useY2AxisTitle as jest.Mock).mockReturnValue('Y2 Axis Title');
|
||||
});
|
||||
|
||||
it('should return the expected axis titles for vertical layout', () => {
|
||||
const { result } = renderHook(() => useAxisTitles(defaultProps));
|
||||
|
||||
expect(result.current).toEqual({
|
||||
xAxisTitle: 'X Axis Title',
|
||||
yAxisTitle: 'Y Axis Title',
|
||||
y2AxisTitle: 'Y2 Axis Title'
|
||||
});
|
||||
|
||||
// Verify the hook calls
|
||||
expect(useXAxisTitle).toHaveBeenCalledWith({
|
||||
xAxis: defaultProps.selectedAxis.x,
|
||||
columnLabelFormats: defaultProps.columnLabelFormats,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
xAxisAxisTitle: defaultProps.xAxisAxisTitle,
|
||||
xAxisShowAxisTitle: defaultProps.xAxisShowAxisTitle,
|
||||
selectedAxis: defaultProps.selectedAxis
|
||||
});
|
||||
|
||||
expect(useYAxisTitle).toHaveBeenCalledWith({
|
||||
yAxis: defaultProps.selectedAxis.y,
|
||||
columnLabelFormats: defaultProps.columnLabelFormats,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
yAxisAxisTitle: defaultProps.yAxisAxisTitle,
|
||||
yAxisShowAxisTitle: defaultProps.yAxisShowAxisTitle,
|
||||
selectedAxis: defaultProps.selectedAxis
|
||||
});
|
||||
|
||||
expect(useY2AxisTitle).toHaveBeenCalledWith({
|
||||
y2Axis: defaultProps.selectedAxis.y2,
|
||||
columnLabelFormats: defaultProps.columnLabelFormats,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
y2AxisAxisTitle: defaultProps.y2AxisAxisTitle,
|
||||
y2AxisShowAxisTitle: defaultProps.y2AxisShowAxisTitle
|
||||
});
|
||||
});
|
||||
|
||||
it('should swap X and Y axis titles for horizontal bar layout', () => {
|
||||
const horizontalProps = {
|
||||
...defaultProps,
|
||||
barLayout: 'horizontal' as const,
|
||||
selectedChartType: ChartType.Bar
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useAxisTitles(horizontalProps));
|
||||
|
||||
// When horizontal, the titles should be swapped
|
||||
expect(result.current).toEqual({
|
||||
xAxisTitle: 'Y Axis Title', // Swapped
|
||||
yAxisTitle: 'X Axis Title', // Swapped
|
||||
y2AxisTitle: 'Y2 Axis Title'
|
||||
});
|
||||
});
|
||||
|
||||
it('should not swap axis titles for non-bar charts, even with horizontal layout', () => {
|
||||
const nonBarProps = {
|
||||
...defaultProps,
|
||||
barLayout: 'horizontal' as const,
|
||||
selectedChartType: ChartType.Line // Line chart, not Bar
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useAxisTitles(nonBarProps));
|
||||
|
||||
// Should not swap for non-bar charts
|
||||
expect(result.current).toEqual({
|
||||
xAxisTitle: 'X Axis Title',
|
||||
yAxisTitle: 'Y Axis Title',
|
||||
y2AxisTitle: 'Y2 Axis Title'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty y2 axis properly', () => {
|
||||
const noY2AxisProps = {
|
||||
...defaultProps,
|
||||
selectedAxis: {
|
||||
x: ['date'],
|
||||
y: ['value']
|
||||
} as ChartEncodes
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useAxisTitles(noY2AxisProps));
|
||||
|
||||
expect(useY2AxisTitle).toHaveBeenCalledWith({
|
||||
y2Axis: [],
|
||||
columnLabelFormats: defaultProps.columnLabelFormats,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
y2AxisAxisTitle: defaultProps.y2AxisAxisTitle,
|
||||
y2AxisShowAxisTitle: defaultProps.y2AxisShowAxisTitle
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass isSupportedChartForAxisTitles as false for unsupported chart types', () => {
|
||||
const unsupportedChartProps = {
|
||||
...defaultProps,
|
||||
selectedChartType: ChartType.Pie // Pie is not in the supported types list
|
||||
};
|
||||
|
||||
renderHook(() => useAxisTitles(unsupportedChartProps));
|
||||
|
||||
// All hooks should be called with isSupportedChartForAxisTitles = false
|
||||
expect(useXAxisTitle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSupportedChartForAxisTitles: false
|
||||
})
|
||||
);
|
||||
|
||||
expect(useYAxisTitle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSupportedChartForAxisTitles: false
|
||||
})
|
||||
);
|
||||
|
||||
expect(useY2AxisTitle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSupportedChartForAxisTitles: false
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should test all supported chart types for axis titles', () => {
|
||||
// Test each supported chart type
|
||||
const supportedTypes = [ChartType.Bar, ChartType.Line, ChartType.Scatter, ChartType.Combo];
|
||||
|
||||
supportedTypes.forEach((chartType) => {
|
||||
(useXAxisTitle as jest.Mock).mockClear();
|
||||
(useYAxisTitle as jest.Mock).mockClear();
|
||||
(useY2AxisTitle as jest.Mock).mockClear();
|
||||
|
||||
const chartTypeProps = {
|
||||
...defaultProps,
|
||||
selectedChartType: chartType
|
||||
};
|
||||
|
||||
renderHook(() => useAxisTitles(chartTypeProps));
|
||||
|
||||
// For each supported type, isSupportedChartForAxisTitles should be true
|
||||
expect(useXAxisTitle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSupportedChartForAxisTitles: true
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,193 @@
|
|||
import { renderHook } from '@testing-library/react';
|
||||
import { useXAxisTitle } from './useXAxisTitle';
|
||||
import { AXIS_TITLE_SEPARATOR } from './axisHelper';
|
||||
import { formatLabel } from '@/lib/columnFormatter';
|
||||
import { truncateWithEllipsis } from './titleHelpers';
|
||||
import { ChartType } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { ChartEncodes, IColumnLabelFormat } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { SimplifiedColumnType } from '@/api/asset_interfaces/metric';
|
||||
|
||||
// Mock the dependencies
|
||||
jest.mock('@/lib/columnFormatter', () => ({
|
||||
formatLabel: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('./titleHelpers', () => ({
|
||||
truncateWithEllipsis: jest.fn()
|
||||
}));
|
||||
|
||||
describe('useXAxisTitle', () => {
|
||||
const defaultProps = {
|
||||
xAxis: ['date', 'category'],
|
||||
columnLabelFormats: {
|
||||
date: {
|
||||
columnType: 'date' as SimplifiedColumnType,
|
||||
style: 'date' as const
|
||||
},
|
||||
category: {
|
||||
columnType: 'string' as SimplifiedColumnType,
|
||||
style: 'string' as const
|
||||
},
|
||||
value: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
} as Record<string, IColumnLabelFormat>,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
xAxisShowAxisTitle: true,
|
||||
xAxisAxisTitle: '',
|
||||
selectedAxis: {
|
||||
x: ['date', 'category'],
|
||||
y: ['value']
|
||||
} as ChartEncodes
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Default mock implementations
|
||||
(formatLabel as jest.Mock).mockImplementation((value) => `formatted_${value}`);
|
||||
(truncateWithEllipsis as jest.Mock).mockImplementation((text) => text);
|
||||
});
|
||||
|
||||
it('should return empty string when chart type is not supported', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
isSupportedChartForAxisTitles: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useXAxisTitle(props));
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty string when xAxisShowAxisTitle is false', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
xAxisShowAxisTitle: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useXAxisTitle(props));
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty string when xAxisAxisTitle is empty string', () => {
|
||||
// This is actually testing the default behavior since xAxisAxisTitle is
|
||||
// already an empty string in defaultProps, but keeping for clarity
|
||||
const { result } = renderHook(() => useXAxisTitle(defaultProps));
|
||||
|
||||
// When xAxisAxisTitle is empty, the hook should return empty string without calling formatLabel
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use the provided xAxisAxisTitle when available', () => {
|
||||
const customTitle = 'Custom X-Axis Title';
|
||||
const props = {
|
||||
...defaultProps,
|
||||
xAxisAxisTitle: customTitle
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Truncated Custom Title');
|
||||
|
||||
const { result } = renderHook(() => useXAxisTitle(props));
|
||||
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith(customTitle);
|
||||
expect(result.current).toBe('Truncated Custom Title');
|
||||
expect(formatLabel).not.toHaveBeenCalled(); // Should not format labels when custom title is provided
|
||||
});
|
||||
|
||||
it('should generate title from x-axis columns when no custom title is provided', () => {
|
||||
(formatLabel as jest.Mock)
|
||||
.mockReturnValueOnce('Formatted Date')
|
||||
.mockReturnValueOnce('Formatted Category');
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Date | Formatted Category');
|
||||
|
||||
// Modified props to ensure formatLabel is called
|
||||
const modifiedProps = {
|
||||
...defaultProps,
|
||||
xAxisAxisTitle: null, // Set to null instead of empty string to ensure formatLabel is called
|
||||
isSupportedChartForAxisTitles: true,
|
||||
xAxisShowAxisTitle: true
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useXAxisTitle(modifiedProps));
|
||||
|
||||
// Should format each x-axis column
|
||||
expect(formatLabel).toHaveBeenCalledWith('date', defaultProps.columnLabelFormats['date'], true);
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'category',
|
||||
defaultProps.columnLabelFormats['category'],
|
||||
true
|
||||
);
|
||||
|
||||
// Should generate title with separator
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Date | Formatted Category');
|
||||
expect(result.current).toBe('Formatted Date | Formatted Category');
|
||||
});
|
||||
|
||||
it('should handle single x-axis column correctly', () => {
|
||||
const singleAxisProps = {
|
||||
...defaultProps,
|
||||
xAxis: ['date'],
|
||||
selectedAxis: {
|
||||
x: ['date'],
|
||||
y: ['value']
|
||||
} as ChartEncodes,
|
||||
xAxisAxisTitle: null // Set to null instead of empty string to ensure formatLabel is called
|
||||
};
|
||||
|
||||
(formatLabel as jest.Mock).mockReturnValue('Formatted Date');
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Date');
|
||||
|
||||
const { result } = renderHook(() => useXAxisTitle(singleAxisProps));
|
||||
|
||||
expect(formatLabel).toHaveBeenCalledWith('date', defaultProps.columnLabelFormats['date'], true);
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Date');
|
||||
expect(result.current).toBe('Formatted Date');
|
||||
});
|
||||
|
||||
it('should correctly memoize the result', () => {
|
||||
// Modified props to ensure formatLabel is called
|
||||
const modifiedProps = {
|
||||
...defaultProps,
|
||||
xAxisAxisTitle: null // Set to null instead of empty string to ensure formatLabel is called
|
||||
};
|
||||
|
||||
const { result, rerender } = renderHook(() => useXAxisTitle(modifiedProps));
|
||||
const initialResult = result.current;
|
||||
|
||||
// Rerender with the same props
|
||||
rerender();
|
||||
|
||||
// Result should be the same instance (memoized)
|
||||
expect(result.current).toBe(initialResult);
|
||||
|
||||
// formatLabel and truncateWithEllipsis should only be called once
|
||||
expect(formatLabel).toHaveBeenCalledTimes(2); // Once for each x-axis column
|
||||
});
|
||||
|
||||
it('should update when dependencies change', () => {
|
||||
const { result, rerender } = renderHook((props) => useXAxisTitle(props), {
|
||||
initialProps: defaultProps
|
||||
});
|
||||
|
||||
// Change a dependency
|
||||
const newProps = {
|
||||
...defaultProps,
|
||||
xAxisAxisTitle: 'New Title'
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Truncated New Title');
|
||||
|
||||
rerender(newProps);
|
||||
|
||||
// Result should update
|
||||
expect(result.current).toBe('Truncated New Title');
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('New Title');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,197 @@
|
|||
import { renderHook } from '@testing-library/react';
|
||||
import { useY2AxisTitle } from './useY2AxisTitle';
|
||||
import { AXIS_TITLE_SEPARATOR } from './axisHelper';
|
||||
import { formatLabel } from '@/lib/columnFormatter';
|
||||
import { truncateWithEllipsis } from './titleHelpers';
|
||||
import type { IColumnLabelFormat } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { SimplifiedColumnType } from '@/api/asset_interfaces/metric';
|
||||
|
||||
// Mock the dependencies
|
||||
jest.mock('@/lib/columnFormatter', () => ({
|
||||
formatLabel: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('./titleHelpers', () => ({
|
||||
truncateWithEllipsis: jest.fn()
|
||||
}));
|
||||
|
||||
describe('useY2AxisTitle', () => {
|
||||
const defaultProps = {
|
||||
y2Axis: ['revenue', 'profit'],
|
||||
columnLabelFormats: {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
profit: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
count: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
} as Record<string, IColumnLabelFormat>,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
y2AxisShowAxisTitle: true,
|
||||
y2AxisAxisTitle: ''
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Default mock implementations
|
||||
(formatLabel as jest.Mock).mockImplementation((value) => `formatted_${value}`);
|
||||
(truncateWithEllipsis as jest.Mock).mockImplementation((text) => text);
|
||||
});
|
||||
|
||||
it('should return empty string when chart type is not supported', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
isSupportedChartForAxisTitles: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useY2AxisTitle(props));
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty string when y2AxisShowAxisTitle is false', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
y2AxisShowAxisTitle: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useY2AxisTitle(props));
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use the provided y2AxisAxisTitle when available', () => {
|
||||
const customTitle = 'Custom Y2-Axis Title';
|
||||
const props = {
|
||||
...defaultProps,
|
||||
y2AxisAxisTitle: customTitle
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Truncated Custom Title');
|
||||
|
||||
const { result } = renderHook(() => useY2AxisTitle(props));
|
||||
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith(customTitle);
|
||||
expect(result.current).toBe('Truncated Custom Title');
|
||||
expect(formatLabel).not.toHaveBeenCalled(); // Should not format labels when custom title is provided
|
||||
});
|
||||
|
||||
it('should generate title from y2-axis columns when no custom title is provided', () => {
|
||||
(formatLabel as jest.Mock)
|
||||
.mockReturnValueOnce('Formatted Revenue')
|
||||
.mockReturnValueOnce('Formatted Profit');
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Revenue | Formatted Profit');
|
||||
|
||||
// Set y2AxisAxisTitle to null to test the fallback behavior
|
||||
const props = {
|
||||
...defaultProps,
|
||||
y2AxisAxisTitle: null
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useY2AxisTitle(props));
|
||||
|
||||
// Should format each y2-axis column
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'revenue',
|
||||
defaultProps.columnLabelFormats['revenue'],
|
||||
true
|
||||
);
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'profit',
|
||||
defaultProps.columnLabelFormats['profit'],
|
||||
true
|
||||
);
|
||||
|
||||
// Should generate title with separator
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Revenue | Formatted Profit');
|
||||
expect(result.current).toBe('Formatted Revenue | Formatted Profit');
|
||||
});
|
||||
|
||||
it('should handle single y2-axis column correctly', () => {
|
||||
const singleAxisProps = {
|
||||
...defaultProps,
|
||||
y2Axis: ['revenue'],
|
||||
y2AxisAxisTitle: null
|
||||
};
|
||||
|
||||
(formatLabel as jest.Mock).mockReturnValue('Formatted Revenue');
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Revenue');
|
||||
|
||||
const { result } = renderHook(() => useY2AxisTitle(singleAxisProps));
|
||||
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'revenue',
|
||||
defaultProps.columnLabelFormats['revenue'],
|
||||
true
|
||||
);
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Revenue');
|
||||
expect(result.current).toBe('Formatted Revenue');
|
||||
});
|
||||
|
||||
it('should correctly memoize the result', () => {
|
||||
// Set y2AxisAxisTitle to null to test the fallback behavior
|
||||
const props = {
|
||||
...defaultProps,
|
||||
y2AxisAxisTitle: null
|
||||
};
|
||||
|
||||
const { result, rerender } = renderHook(() => useY2AxisTitle(props));
|
||||
const initialResult = result.current;
|
||||
|
||||
// Rerender with the same props
|
||||
rerender();
|
||||
|
||||
// Result should be the same instance (memoized)
|
||||
expect(result.current).toBe(initialResult);
|
||||
|
||||
// formatLabel and truncateWithEllipsis should only be called once per render
|
||||
expect(formatLabel).toHaveBeenCalledTimes(2); // Once for each y2-axis column
|
||||
});
|
||||
|
||||
it('should update when dependencies change', () => {
|
||||
const { result, rerender } = renderHook((props) => useY2AxisTitle(props), {
|
||||
initialProps: defaultProps
|
||||
});
|
||||
|
||||
// Change a dependency
|
||||
const newProps = {
|
||||
...defaultProps,
|
||||
y2AxisAxisTitle: 'New Title'
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Truncated New Title');
|
||||
|
||||
rerender(newProps);
|
||||
|
||||
// Result should update
|
||||
expect(result.current).toBe('Truncated New Title');
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('New Title');
|
||||
});
|
||||
|
||||
it('should handle empty y2Axis array', () => {
|
||||
const emptyAxisProps = {
|
||||
...defaultProps,
|
||||
y2Axis: [],
|
||||
y2AxisAxisTitle: null
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('');
|
||||
|
||||
const { result } = renderHook(() => useY2AxisTitle(emptyAxisProps));
|
||||
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('');
|
||||
expect(result.current).toBe('');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,246 @@
|
|||
import { renderHook } from '@testing-library/react';
|
||||
import { useYAxisTitle } from './useYAxisTitle';
|
||||
import { AXIS_TITLE_SEPARATOR } from './axisHelper';
|
||||
import { formatLabel } from '@/lib/columnFormatter';
|
||||
import { truncateWithEllipsis } from './titleHelpers';
|
||||
import type { ChartEncodes, IColumnLabelFormat } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { SimplifiedColumnType } from '@/api/asset_interfaces/metric';
|
||||
|
||||
// Mock the dependencies
|
||||
jest.mock('@/lib/columnFormatter', () => ({
|
||||
formatLabel: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('./titleHelpers', () => ({
|
||||
truncateWithEllipsis: jest.fn()
|
||||
}));
|
||||
|
||||
describe('useYAxisTitle', () => {
|
||||
const defaultProps = {
|
||||
yAxis: ['value', 'count'],
|
||||
columnLabelFormats: {
|
||||
date: {
|
||||
columnType: 'date' as SimplifiedColumnType,
|
||||
style: 'date' as const
|
||||
},
|
||||
category: {
|
||||
columnType: 'string' as SimplifiedColumnType,
|
||||
style: 'string' as const
|
||||
},
|
||||
value: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
},
|
||||
count: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
} as Record<string, IColumnLabelFormat>,
|
||||
isSupportedChartForAxisTitles: true,
|
||||
yAxisShowAxisTitle: true,
|
||||
yAxisAxisTitle: '',
|
||||
selectedAxis: {
|
||||
x: ['date', 'category'],
|
||||
y: ['value', 'count']
|
||||
} as ChartEncodes
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Default mock implementations
|
||||
(formatLabel as jest.Mock).mockImplementation((value) => `formatted_${value}`);
|
||||
(truncateWithEllipsis as jest.Mock).mockImplementation((text) => text);
|
||||
});
|
||||
|
||||
it('should return empty string when chart type is not supported', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
isSupportedChartForAxisTitles: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(props));
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty string when yAxisShowAxisTitle is false', () => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
yAxisShowAxisTitle: false
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(props));
|
||||
expect(result.current).toBe('');
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use the provided yAxisAxisTitle when available', () => {
|
||||
const customTitle = 'Custom Y-Axis Title';
|
||||
const props = {
|
||||
...defaultProps,
|
||||
yAxisAxisTitle: customTitle
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Truncated Custom Title');
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(props));
|
||||
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith(customTitle);
|
||||
expect(result.current).toBe('Truncated Custom Title');
|
||||
expect(formatLabel).not.toHaveBeenCalled(); // Should not format labels when custom title is provided
|
||||
});
|
||||
|
||||
it('should generate title from y-axis columns when no custom title is provided', () => {
|
||||
(formatLabel as jest.Mock)
|
||||
.mockReturnValueOnce('Formatted Value')
|
||||
.mockReturnValueOnce('Formatted Count');
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Value | Formatted Count');
|
||||
|
||||
// Modified props to ensure formatLabel is called
|
||||
const modifiedProps = {
|
||||
...defaultProps,
|
||||
yAxisAxisTitle: null // Set to null instead of empty string to ensure formatLabel is called
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(modifiedProps));
|
||||
|
||||
// Should format each y-axis column
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'value',
|
||||
defaultProps.columnLabelFormats['value'],
|
||||
true
|
||||
);
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'count',
|
||||
defaultProps.columnLabelFormats['count'],
|
||||
true
|
||||
);
|
||||
|
||||
// Should generate title with separator
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Value | Formatted Count');
|
||||
expect(result.current).toBe('Formatted Value | Formatted Count');
|
||||
});
|
||||
|
||||
it('should handle single y-axis column correctly', () => {
|
||||
const singleAxisProps = {
|
||||
...defaultProps,
|
||||
yAxis: ['value'],
|
||||
selectedAxis: {
|
||||
x: ['date', 'category'],
|
||||
y: ['value']
|
||||
} as ChartEncodes,
|
||||
yAxisAxisTitle: null // Set to null instead of empty string to ensure formatLabel is called
|
||||
};
|
||||
|
||||
(formatLabel as jest.Mock).mockReturnValue('Formatted Value');
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Value');
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(singleAxisProps));
|
||||
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'value',
|
||||
defaultProps.columnLabelFormats['value'],
|
||||
true
|
||||
);
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Value');
|
||||
expect(result.current).toBe('Formatted Value');
|
||||
});
|
||||
|
||||
it('should correctly memoize the result', () => {
|
||||
// Modified props to ensure formatLabel is called
|
||||
const modifiedProps = {
|
||||
...defaultProps,
|
||||
yAxisAxisTitle: null // Set to null instead of empty string to ensure formatLabel is called
|
||||
};
|
||||
|
||||
const { result, rerender } = renderHook(() => useYAxisTitle(modifiedProps));
|
||||
const initialResult = result.current;
|
||||
|
||||
// Rerender with the same props
|
||||
rerender();
|
||||
|
||||
// Result should be the same instance (memoized)
|
||||
expect(result.current).toBe(initialResult);
|
||||
|
||||
// formatLabel and truncateWithEllipsis should only be called once for each y-axis item
|
||||
expect(formatLabel).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should update when dependencies change', () => {
|
||||
const { result, rerender } = renderHook((props) => useYAxisTitle(props), {
|
||||
initialProps: defaultProps
|
||||
});
|
||||
|
||||
// Change a dependency
|
||||
const newProps = {
|
||||
...defaultProps,
|
||||
yAxisAxisTitle: 'New Title'
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Truncated New Title');
|
||||
|
||||
rerender(newProps);
|
||||
|
||||
// Result should update
|
||||
expect(result.current).toBe('Truncated New Title');
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('New Title');
|
||||
});
|
||||
|
||||
it('should handle empty yAxis array', () => {
|
||||
const emptyAxisProps = {
|
||||
...defaultProps,
|
||||
yAxis: [],
|
||||
selectedAxis: {
|
||||
x: ['date', 'category'],
|
||||
y: []
|
||||
} as ChartEncodes,
|
||||
yAxisAxisTitle: null
|
||||
};
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('');
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(emptyAxisProps));
|
||||
|
||||
expect(formatLabel).not.toHaveBeenCalled();
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('');
|
||||
expect(result.current).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly use selectedAxis.y property for formatting', () => {
|
||||
// Test case where yAxis array and selectedAxis.y are different
|
||||
const differentAxisProps = {
|
||||
...defaultProps,
|
||||
yAxis: ['count'], // This is different from selectedAxis.y
|
||||
selectedAxis: {
|
||||
x: ['date'],
|
||||
y: ['value', 'count'] // This should be used for formatting
|
||||
} as ChartEncodes,
|
||||
yAxisAxisTitle: null
|
||||
};
|
||||
|
||||
(formatLabel as jest.Mock)
|
||||
.mockReturnValueOnce('Formatted Value')
|
||||
.mockReturnValueOnce('Formatted Count');
|
||||
|
||||
(truncateWithEllipsis as jest.Mock).mockReturnValue('Formatted Value | Formatted Count');
|
||||
|
||||
const { result } = renderHook(() => useYAxisTitle(differentAxisProps));
|
||||
|
||||
// Should use selectedAxis.y for formatting, not yAxis
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'value',
|
||||
defaultProps.columnLabelFormats['value'],
|
||||
true
|
||||
);
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
'count',
|
||||
defaultProps.columnLabelFormats['count'],
|
||||
true
|
||||
);
|
||||
expect(truncateWithEllipsis).toHaveBeenCalledWith('Formatted Value | Formatted Count');
|
||||
expect(result.current).toBe('Formatted Value | Formatted Count');
|
||||
});
|
||||
});
|
|
@ -9,7 +9,7 @@ import { itemAnimationConfig } from './animationConfig';
|
|||
import { StatusIndicator } from '@/components/ui/indicators';
|
||||
import { FileCard } from '../card/FileCard';
|
||||
import { TextAndVersionPill } from '../typography/TextAndVersionPill';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
export const StreamingMessage_File: React.FC<{
|
||||
isSelectedFile: boolean;
|
||||
responseMessage: BusterChatResponseMessage_file;
|
||||
|
@ -26,6 +26,7 @@ export const StreamingMessage_File: React.FC<{
|
|||
<AnimatePresence initial={!isCompletedStream}>
|
||||
<motion.div id={id} {...itemAnimationConfig}>
|
||||
<FileCard
|
||||
className={cn(isSelectedFile && 'border-foreground shadow-md')}
|
||||
fileName={<TextAndVersionPill fileName={file_name} versionNumber={version_number} />}>
|
||||
<StreamingMessageBody metadata={metadata} />
|
||||
</FileCard>
|
||||
|
|
|
@ -5,8 +5,8 @@ import { Text } from './Text';
|
|||
export const TextAndVersionPill = React.memo(
|
||||
({ fileName, versionNumber }: { fileName: string; versionNumber: number }) => {
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Text>{fileName}</Text>
|
||||
<div className="flex w-full items-center gap-1.5">
|
||||
<Text truncate>{fileName}</Text>
|
||||
{versionNumber !== undefined && <VersionPill version_number={versionNumber} />}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
} from './chatStreamMessageHelper';
|
||||
import { useChatUpdate } from './useChatUpdate';
|
||||
import { prefetchGetMetricDataClient } from '@/api/buster_rest/metrics';
|
||||
import { useOriginalMetricStore } from '@/context/Metrics/useOriginalMetricStore';
|
||||
|
||||
export const useChatStreamMessage = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
'use client';
|
||||
|
||||
import type { IBusterMetric } from '@/api/asset_interfaces/metric';
|
||||
import { create } from 'zustand';
|
||||
import { compareObjectsByKeys } from '@/lib/objects';
|
||||
import { useGetMetric } from '@/api/buster_rest/metrics/queryRequests';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { useMemoizedFn, useMount } from '@/hooks';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { metricsQueryKeys } from '@/api/query_keys/metric';
|
||||
|
||||
export const useOriginalMetricStore = create<{
|
||||
type OriginalMetricStore = {
|
||||
originalMetrics: Record<string, IBusterMetric>;
|
||||
bulkAddOriginalMetrics: (metrics: Record<string, IBusterMetric>) => void;
|
||||
setOriginalMetric: (metric: IBusterMetric) => void;
|
||||
getOriginalMetric: (metricId: string) => IBusterMetric | undefined;
|
||||
removeOriginalMetric: (metricId: string) => void;
|
||||
}>((set, get) => ({
|
||||
};
|
||||
|
||||
export const useOriginalMetricStore = create<OriginalMetricStore>((set, get) => ({
|
||||
originalMetrics: {},
|
||||
bulkAddOriginalMetrics: (metrics: Record<string, IBusterMetric>) =>
|
||||
set((prev) => ({
|
||||
originalMetrics: {
|
||||
...prev.originalMetrics,
|
||||
...metrics
|
||||
}
|
||||
})),
|
||||
setOriginalMetric: (metric: IBusterMetric) =>
|
||||
set((state) => ({
|
||||
originalMetrics: {
|
||||
|
@ -63,3 +75,16 @@ export const useIsMetricChanged = ({ metricId }: { metricId: string }) => {
|
|||
])
|
||||
};
|
||||
};
|
||||
|
||||
export const HydrationBoundaryMetricStore: React.FC<{
|
||||
children: React.ReactNode;
|
||||
metric?: OriginalMetricStore['originalMetrics'][string];
|
||||
}> = ({ children, metric }) => {
|
||||
const setOriginalMetrics = useOriginalMetricStore((x) => x.setOriginalMetric);
|
||||
|
||||
useMount(() => {
|
||||
if (metric) setOriginalMetrics(metric);
|
||||
});
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
|
@ -50,7 +50,6 @@ import { ShareMenuContent } from '@/components/features/ShareMenu/ShareMenuConte
|
|||
import { canEdit, getIsEffectiveOwner, getIsOwner } from '@/lib/share';
|
||||
import { getShareAssetConfig } from '@/components/features/ShareMenu/helpers';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { assetParamsToRoute } from '@/layouts/ChatLayout/ChatLayoutContext/helpers';
|
||||
|
||||
export const ThreeDotMenuButton = React.memo(({ metricId }: { metricId: string }) => {
|
||||
|
|
|
@ -14,10 +14,7 @@ const DEFAULT_DATE_FORMAT = 'll';
|
|||
|
||||
export const formatLabel = (
|
||||
textProp: string | number | Date | null | undefined,
|
||||
props: ColumnLabelFormat = {
|
||||
columnType: 'text',
|
||||
style: 'string'
|
||||
},
|
||||
props: ColumnLabelFormat = DEFAULT_COLUMN_LABEL_FORMAT,
|
||||
useKeyFormatter: boolean = false
|
||||
): string => {
|
||||
const {
|
||||
|
|
Loading…
Reference in New Issue