mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 20:19:13 +08:00
feat: adds error toggle in top error page (#7773)
Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
parent
75d86cea60
commit
130ff925bd
@ -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>
|
||||||
|
@ -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,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user