From c03541cd6c020f921d8ddf6ec234455b8d73a54a Mon Sep 17 00:00:00 2001 From: Amlan Kumar Nandy <45410599+amlannandy@users.noreply.github.com> Date: Fri, 9 May 2025 11:41:41 +0700 Subject: [PATCH] chore: improve error handling and loading states in summary view of metrics explorer (#7862) --- .../MetricsExplorer/Summary/MetricsTable.tsx | 38 ++-- .../Summary/MetricsTreemap.tsx | 11 +- .../MetricsExplorer/Summary/Summary.tsx | 14 +- .../Summary/__tests__/MetricsTable.test.tsx | 200 ++++++++++++++++++ .../Summary/__tests__/MetricsTreemap.test.tsx | 139 ++++++++++++ 5 files changed, 385 insertions(+), 17 deletions(-) create mode 100644 frontend/src/container/MetricsExplorer/Summary/__tests__/MetricsTable.test.tsx create mode 100644 frontend/src/container/MetricsExplorer/Summary/__tests__/MetricsTreemap.test.tsx diff --git a/frontend/src/container/MetricsExplorer/Summary/MetricsTable.tsx b/frontend/src/container/MetricsExplorer/Summary/MetricsTable.tsx index d103fc99de..93aff3ad33 100644 --- a/frontend/src/container/MetricsExplorer/Summary/MetricsTable.tsx +++ b/frontend/src/container/MetricsExplorer/Summary/MetricsTable.tsx @@ -50,27 +50,39 @@ function MetricsTable({ return (
-
- - List View - - - - -
+ {!isError && !isLoading && ( +
+ + List View + + + + +
+ )} } />, + indicator: ( + } + /> + ), }} dataSource={data} columns={metricsTableColumns} locale={{ emptyText: isLoading ? null : ( -
+
thinking-emoji +
+ +
); } @@ -66,6 +68,7 @@ function MetricsTreemap({ return ( ); @@ -75,13 +78,17 @@ function MetricsTreemap({ return ( ); } return ( -
+
Proportion View isMetricsError || (metricsData && metricsData.statusCode !== 200), + [isMetricsError, metricsData], + ); + const { data: treeMapData, isLoading: isTreeMapLoading, @@ -132,6 +137,11 @@ function Summary(): JSX.Element { enabled: !!metricsTreemapQuery && !isInspectModalOpen, }); + const isProportionViewError = useMemo( + () => isTreeMapError || treeMapData?.statusCode !== 200, + [isTreeMapError, treeMapData], + ); + const handleFilterChange = useCallback( (value: TagFilter) => { handleChangeQueryData('filters', value); @@ -208,13 +218,13 @@ function Summary(): JSX.Element { { + jest + .spyOn(useGetMetricsListFilterValues, 'useGetMetricsListFilterValues') + .mockReturnValue({ + data: { + statusCode: 200, + payload: { + status: 'success', + data: { + filterValues: ['metric1', 'metric2'], + }, + }, + }, + isLoading: false, + isError: false, + } as any); + + it('renders table with data correctly', () => { + render( + + + + + , + ); + + expect(screen.getByText('List View')).toBeInTheDocument(); + expect(screen.getByText('Metric 1')).toBeInTheDocument(); + expect(screen.getByText('Metric 2')).toBeInTheDocument(); + }); + + it('shows loading state', () => { + render( + + + + + , + ); + + expect(screen.getByTestId('metrics-table-loading-state')).toBeInTheDocument(); + }); + + it('shows error state', () => { + render( + + + + + , + ); + + expect(screen.getByTestId('metrics-table-error-state')).toBeInTheDocument(); + expect( + screen.getByText( + 'Error fetching metrics. If the problem persists, please contact support.', + ), + ).toBeInTheDocument(); + }); + + it('shows empty state when no data', () => { + render( + + + + + , + ); + + expect(screen.getByTestId('metrics-table-empty-state')).toBeInTheDocument(); + expect( + screen.getByText( + 'This query had no results. Edit your query and try again!', + ), + ).toBeInTheDocument(); + }); + + it('calls openMetricDetails when row is clicked', () => { + const mockOpenMetricDetails = jest.fn(); + render( + + + + + , + ); + + fireEvent.click(screen.getByText('Metric 1')); + expect(mockOpenMetricDetails).toHaveBeenCalledWith('metric1'); + }); + + it('calls setOrderBy when column header is clicked', () => { + const mockSetOrderBy = jest.fn(); + render( + + + + + , + ); + + const samplesHeader = screen.getByText('SAMPLES'); + fireEvent.click(samplesHeader); + + expect(mockSetOrderBy).toHaveBeenCalledWith({ + columnName: 'samples', + order: 'asc', + }); + }); +}); diff --git a/frontend/src/container/MetricsExplorer/Summary/__tests__/MetricsTreemap.test.tsx b/frontend/src/container/MetricsExplorer/Summary/__tests__/MetricsTreemap.test.tsx new file mode 100644 index 0000000000..78465e4e19 --- /dev/null +++ b/frontend/src/container/MetricsExplorer/Summary/__tests__/MetricsTreemap.test.tsx @@ -0,0 +1,139 @@ +import { render, screen } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import store from 'store'; + +import MetricsTreemap from '../MetricsTreemap'; +import { TreemapViewType } from '../types'; + +jest.mock('d3-hierarchy', () => ({ + stratify: jest.fn().mockReturnValue({ + id: jest.fn().mockReturnValue({ + parentId: jest.fn().mockReturnValue( + jest.fn().mockReturnValue({ + sum: jest.fn().mockReturnValue({ + descendants: jest.fn().mockReturnValue([]), + eachBefore: jest.fn().mockReturnValue([]), + }), + }), + ), + }), + }), + treemapBinary: jest.fn(), +})); +jest.mock('react-use', () => ({ + useWindowSize: jest.fn().mockReturnValue({ width: 1000, height: 1000 }), +})); + +const mockData = [ + { + metric_name: 'Metric 1', + percentage: 0.5, + total_value: 15, + }, + { + metric_name: 'Metric 2', + percentage: 0.6, + total_value: 10, + }, +]; + +describe('MetricsTreemap', () => { + it('renders treemap with data correctly', () => { + render( + + + + + , + ); + + expect(screen.getByText('Proportion View')).toBeInTheDocument(); + }); + + it('shows loading state', () => { + render( + + + + + , + ); + + expect( + screen.getByTestId('metrics-treemap-loading-state'), + ).toBeInTheDocument(); + }); + + it('shows error state', () => { + render( + + + + + , + ); + + expect(screen.getByTestId('metrics-treemap-error-state')).toBeInTheDocument(); + expect( + screen.getByText( + 'Error fetching metrics. If the problem persists, please contact support.', + ), + ).toBeInTheDocument(); + }); + + it('shows empty state when no data', () => { + render( + + + + + , + ); + + expect(screen.getByTestId('metrics-treemap-empty-state')).toBeInTheDocument(); + expect(screen.getByText('No metrics found')).toBeInTheDocument(); + }); +});