feat: adds error toggle in top error page (#7773)

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
Shivanshu Raj Shrivastava 2025-04-30 11:05:30 +05:30 committed by GitHub
parent 75d86cea60
commit 130ff925bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 162 additions and 43 deletions

View File

@ -1,5 +1,5 @@
import { LoadingOutlined } from '@ant-design/icons'; 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 { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V4 } from 'constants/app'; import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V4 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@ -41,44 +41,61 @@ function TopErrors({
const { startTime: minTime, endTime: maxTime } = timeRange; const { startTime: minTime, endTime: maxTime } = timeRange;
const [endPointName, setSelectedEndPointName] = useState<string>(''); const [endPointName, setSelectedEndPointName] = useState<string>('');
const [showStatusCodeErrors, setShowStatusCodeErrors] = useState<boolean>(
true,
);
const queryPayloads = useMemo( const queryPayloads = useMemo(
() => () =>
getTopErrorsQueryPayload(domainName, minTime, maxTime, { getTopErrorsQueryPayload(
items: endPointName domainName,
? [ minTime,
{ maxTime,
id: '92b8a1c1', {
key: { items: endPointName
dataType: DataTypes.String, ? [
isColumn: false, {
isJSON: false, id: '92b8a1c1',
key: SPAN_ATTRIBUTES.URL_PATH, key: {
type: 'tag', dataType: DataTypes.String,
isColumn: false,
isJSON: false,
key: SPAN_ATTRIBUTES.URL_PATH,
type: 'tag',
},
op: '=',
value: endPointName,
}, },
op: '=', ...initialFilters.items,
value: endPointName, ]
}, : [...initialFilters.items],
...initialFilters.items, op: 'AND',
] },
: [...initialFilters.items], showStatusCodeErrors,
op: 'AND', ),
}), [
[domainName, endPointName, minTime, maxTime, initialFilters], domainName,
endPointName,
minTime,
maxTime,
initialFilters,
showStatusCodeErrors,
],
); );
// Since only one query here
const topErrorsDataQueries = useQueries( const topErrorsDataQueries = useQueries(
queryPayloads.map((payload) => ({ queryPayloads.map((payload) => ({
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN, REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN,
payload, payload,
DEFAULT_ENTITY_VERSION, DEFAULT_ENTITY_VERSION,
showStatusCodeErrors,
], ],
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
GetMetricQueryRange(payload, DEFAULT_ENTITY_VERSION), GetMetricQueryRange(payload, DEFAULT_ENTITY_VERSION),
enabled: !!payload, enabled: !!payload,
staleTime: 60 * 1000, // 1 minute stale time : optimize this part staleTime: 0,
cacheTime: 0,
})), })),
); );
@ -121,7 +138,7 @@ function TopErrors({
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> => queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
GetMetricQueryRange(payload, ENTITY_VERSION_V4), GetMetricQueryRange(payload, ENTITY_VERSION_V4),
enabled: !!payload, 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" parentContainerDiv=".endpoint-details-filters-container"
/> />
</div> </div>
<Tooltip title="Optionally select a specific endpoint to see status message if present"> <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Info size={16} color="white" /> <Switch
</Tooltip> checked={showStatusCodeErrors}
onChange={setShowStatusCodeErrors}
size="small"
/>
<span style={{ color: 'white', fontSize: '14px' }}>
Status Message Exists
</span>
<Tooltip title="When enabled, shows errors that have a status message. When disabled, shows all errors regardless of status message">
<Info size={16} color="white" />
</Tooltip>
</div>
</div> </div>
<div className="endpoints-table-container"> <div className="endpoints-table-container">
<div className="endpoints-table-header"> <div className="endpoints-table-header">
Top Errors{' '} {showStatusCodeErrors ? 'Errors with Status Message' : 'All Errors'}{' '}
<Tooltip title="Shows top 10 errors only when status message is propagated"> <Tooltip
title={
showStatusCodeErrors
? 'Shows errors that have a status message'
: 'Shows all errors regardless of status message'
}
>
<Info size={16} color="white" /> <Info size={16} color="white" />
</Tooltip> </Tooltip>
</div> </div>

View File

@ -211,8 +211,9 @@ describe('TopErrors', () => {
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
const { container } = render(<TopErrors {...mockProps} />); const { container } = render(<TopErrors {...mockProps} />);
// Check if the title is rendered // Check if the title and toggle are rendered
expect(screen.getByText('Top Errors')).toBeInTheDocument(); expect(screen.getByText('Errors with Status Message')).toBeInTheDocument();
expect(screen.getByText('Status Message Exists')).toBeInTheDocument();
// Find the table row and verify content // Find the table row and verify content
const tableBody = container.querySelector('.ant-table-tbody'); const tableBody = container.querySelector('.ant-table-tbody');
@ -290,4 +291,84 @@ describe('TopErrors', () => {
// Check if getTopErrorsQueryPayload was called with updated parameters // Check if getTopErrorsQueryPayload was called with updated parameters
expect(getTopErrorsQueryPayload).toHaveBeenCalled(); expect(getTopErrorsQueryPayload).toHaveBeenCalled();
}); });
it('handles status message toggle correctly', () => {
// eslint-disable-next-line react/jsx-props-no-spreading
render(<TopErrors {...mockProps} />);
// 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(<TopErrors {...mockProps} />);
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,
]),
}),
]),
);
});
}); });

View File

@ -903,6 +903,7 @@ export const getTopErrorsQueryPayload = (
start: number, start: number,
end: number, end: number,
filters: IBuilderQuery['filters'], filters: IBuilderQuery['filters'],
showStatusCodeErrors = true,
): GetQueryResultsProps[] => [ ): GetQueryResultsProps[] => [
{ {
selectedTime: 'GLOBAL_TIME', selectedTime: 'GLOBAL_TIME',
@ -952,18 +953,22 @@ export const getTopErrorsQueryPayload = (
op: 'exists', op: 'exists',
value: '', value: '',
}, },
{ ...(showStatusCodeErrors
id: '75d65388', ? [
key: { {
key: 'status_message', id: '75d65388',
dataType: DataTypes.String, key: {
type: '', key: 'status_message',
isColumn: true, dataType: DataTypes.String,
isJSON: false, type: '',
}, isColumn: true,
op: 'exists', isJSON: false,
value: '', },
}, op: 'exists',
value: '',
},
]
: []),
{ {
id: '4872bf91', id: '4872bf91',
key: { key: {