From 130ff925bdfa4a656390646c8270e66ecba8808d Mon Sep 17 00:00:00 2001 From: Shivanshu Raj Shrivastava Date: Wed, 30 Apr 2025 11:05:30 +0530 Subject: [PATCH] feat: adds error toggle in top error page (#7773) Signed-off-by: Shivanshu Raj Shrivastava --- .../Domains/DomainDetails/TopErrors.tsx | 91 +++++++++++++------ .../__tests__/TopErrors.test.tsx | 85 ++++++++++++++++- .../src/container/ApiMonitoring/utils.tsx | 29 +++--- 3 files changed, 162 insertions(+), 43 deletions(-) diff --git a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx index a8e6de8292..fe1f13f6c5 100644 --- a/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx +++ b/frontend/src/container/ApiMonitoring/Explorer/Domains/DomainDetails/TopErrors.tsx @@ -1,5 +1,5 @@ import { LoadingOutlined } from '@ant-design/icons'; -import { Spin, Table, Tooltip, Typography } from 'antd'; +import { Spin, Switch, Table, Tooltip, Typography } from 'antd'; import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer'; import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V4 } from 'constants/app'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; @@ -41,44 +41,61 @@ function TopErrors({ const { startTime: minTime, endTime: maxTime } = timeRange; const [endPointName, setSelectedEndPointName] = useState(''); + const [showStatusCodeErrors, setShowStatusCodeErrors] = useState( + true, + ); const queryPayloads = useMemo( () => - getTopErrorsQueryPayload(domainName, minTime, maxTime, { - items: endPointName - ? [ - { - id: '92b8a1c1', - key: { - dataType: DataTypes.String, - isColumn: false, - isJSON: false, - key: SPAN_ATTRIBUTES.URL_PATH, - type: 'tag', + getTopErrorsQueryPayload( + domainName, + minTime, + maxTime, + { + items: endPointName + ? [ + { + id: '92b8a1c1', + key: { + dataType: DataTypes.String, + isColumn: false, + isJSON: false, + key: SPAN_ATTRIBUTES.URL_PATH, + type: 'tag', + }, + op: '=', + value: endPointName, }, - op: '=', - value: endPointName, - }, - ...initialFilters.items, - ] - : [...initialFilters.items], - op: 'AND', - }), - [domainName, endPointName, minTime, maxTime, initialFilters], + ...initialFilters.items, + ] + : [...initialFilters.items], + op: 'AND', + }, + showStatusCodeErrors, + ), + [ + domainName, + endPointName, + minTime, + maxTime, + initialFilters, + showStatusCodeErrors, + ], ); - // Since only one query here const topErrorsDataQueries = useQueries( queryPayloads.map((payload) => ({ queryKey: [ REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN, payload, DEFAULT_ENTITY_VERSION, + showStatusCodeErrors, ], queryFn: (): Promise> => GetMetricQueryRange(payload, DEFAULT_ENTITY_VERSION), enabled: !!payload, - staleTime: 60 * 1000, // 1 minute stale time : optimize this part + staleTime: 0, + cacheTime: 0, })), ); @@ -121,7 +138,7 @@ function TopErrors({ queryFn: (): Promise> => GetMetricQueryRange(payload, ENTITY_VERSION_V4), enabled: !!payload, - staleTime: 60 * 1000, // 1 minute stale time : optimize this part + staleTime: 60 * 1000, })), ); @@ -151,15 +168,31 @@ function TopErrors({ parentContainerDiv=".endpoint-details-filters-container" /> - - - +
+ + + Status Message Exists + + + + +
- Top Errors{' '} - + {showStatusCodeErrors ? 'Errors with Status Message' : 'All Errors'}{' '} +
diff --git a/frontend/src/container/ApiMonitoring/__tests__/TopErrors.test.tsx b/frontend/src/container/ApiMonitoring/__tests__/TopErrors.test.tsx index 752cb07cde..0d8732fbd8 100644 --- a/frontend/src/container/ApiMonitoring/__tests__/TopErrors.test.tsx +++ b/frontend/src/container/ApiMonitoring/__tests__/TopErrors.test.tsx @@ -211,8 +211,9 @@ describe('TopErrors', () => { // eslint-disable-next-line react/jsx-props-no-spreading const { container } = render(); - // Check if the title is rendered - expect(screen.getByText('Top Errors')).toBeInTheDocument(); + // Check if the title and toggle are rendered + expect(screen.getByText('Errors with Status Message')).toBeInTheDocument(); + expect(screen.getByText('Status Message Exists')).toBeInTheDocument(); // Find the table row and verify content const tableBody = container.querySelector('.ant-table-tbody'); @@ -290,4 +291,84 @@ describe('TopErrors', () => { // Check if getTopErrorsQueryPayload was called with updated parameters expect(getTopErrorsQueryPayload).toHaveBeenCalled(); }); + + it('handles status message toggle correctly', () => { + // eslint-disable-next-line react/jsx-props-no-spreading + render(); + + // Find the toggle switch + const toggle = screen.getByRole('switch'); + expect(toggle).toBeInTheDocument(); + + // Toggle should be on by default + expect(toggle).toHaveAttribute('aria-checked', 'true'); + + // Click the toggle to turn it off + fireEvent.click(toggle); + + // Check if getTopErrorsQueryPayload was called with showStatusCodeErrors=false + expect(getTopErrorsQueryPayload).toHaveBeenCalledWith( + mockProps.domainName, + mockProps.timeRange.startTime, + mockProps.timeRange.endTime, + expect.any(Object), + false, + ); + + // Title should change + expect(screen.getByText('All Errors')).toBeInTheDocument(); + + // Click the toggle to turn it back on + fireEvent.click(toggle); + + // Check if getTopErrorsQueryPayload was called with showStatusCodeErrors=true + expect(getTopErrorsQueryPayload).toHaveBeenCalledWith( + mockProps.domainName, + mockProps.timeRange.startTime, + mockProps.timeRange.endTime, + expect.any(Object), + true, + ); + + // Title should change back + expect(screen.getByText('Errors with Status Message')).toBeInTheDocument(); + }); + + it('includes toggle state in query key for cache busting', () => { + // eslint-disable-next-line react/jsx-props-no-spreading + render(); + + const toggle = screen.getByRole('switch'); + + // Initial query should include showStatusCodeErrors=true + expect(useQueries).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + queryKey: expect.arrayContaining([ + REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN, + expect.any(Object), + expect.any(String), + true, + ]), + }), + ]), + ); + + // Click toggle + fireEvent.click(toggle); + + // Query should be called with showStatusCodeErrors=false in key + expect(useQueries).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + queryKey: expect.arrayContaining([ + REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN, + expect.any(Object), + expect.any(String), + false, + ]), + }), + ]), + ); + }); }); diff --git a/frontend/src/container/ApiMonitoring/utils.tsx b/frontend/src/container/ApiMonitoring/utils.tsx index 632055ed81..594dedb276 100644 --- a/frontend/src/container/ApiMonitoring/utils.tsx +++ b/frontend/src/container/ApiMonitoring/utils.tsx @@ -903,6 +903,7 @@ export const getTopErrorsQueryPayload = ( start: number, end: number, filters: IBuilderQuery['filters'], + showStatusCodeErrors = true, ): GetQueryResultsProps[] => [ { selectedTime: 'GLOBAL_TIME', @@ -952,18 +953,22 @@ export const getTopErrorsQueryPayload = ( op: 'exists', value: '', }, - { - id: '75d65388', - key: { - key: 'status_message', - dataType: DataTypes.String, - type: '', - isColumn: true, - isJSON: false, - }, - op: 'exists', - value: '', - }, + ...(showStatusCodeErrors + ? [ + { + id: '75d65388', + key: { + key: 'status_message', + dataType: DataTypes.String, + type: '', + isColumn: true, + isJSON: false, + }, + op: 'exists', + value: '', + }, + ] + : []), { id: '4872bf91', key: {