mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 17:39:03 +08:00
parent
cf451d335c
commit
75d86cea60
@ -316,7 +316,7 @@ describe('API Monitoring Utils', () => {
|
||||
{
|
||||
metric: {
|
||||
[SPAN_ATTRIBUTES.URL_PATH]: '/api/test',
|
||||
[SPAN_ATTRIBUTES.STATUS_CODE]: '500',
|
||||
[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE]: '500',
|
||||
status_message: 'Internal Server Error',
|
||||
},
|
||||
values: [[1000000100, '10']],
|
||||
|
@ -35,6 +35,11 @@ function AllEndPoints({
|
||||
initialFilters: IBuilderQuery['filters'];
|
||||
setInitialFiltersEndPointStats: (filters: IBuilderQuery['filters']) => void;
|
||||
}): JSX.Element {
|
||||
const [groupBySearchValue, setGroupBySearchValue] = useState<string>('');
|
||||
const [allAvailableGroupByOptions, setAllAvailableGroupByOptions] = useState<{
|
||||
[key: string]: any;
|
||||
}>({});
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
isLoading: isLoadingGroupByFilters,
|
||||
@ -42,7 +47,7 @@ function AllEndPoints({
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateAttribute: '',
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
searchText: groupBySearchValue,
|
||||
tagType: '',
|
||||
});
|
||||
|
||||
@ -52,34 +57,66 @@ function AllEndPoints({
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
const groupBy = [];
|
||||
const newGroupBy = [];
|
||||
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
const element = (value[index] as unknown) as string;
|
||||
|
||||
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||
(key) => key.key === element,
|
||||
);
|
||||
// Check if the key exists in our cached options first
|
||||
if (allAvailableGroupByOptions[element]) {
|
||||
newGroupBy.push(allAvailableGroupByOptions[element]);
|
||||
} else {
|
||||
// If not found in cache, check the current filtered results
|
||||
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||
(key) => key.key === element,
|
||||
);
|
||||
|
||||
if (key) {
|
||||
groupBy.push(key);
|
||||
if (key) {
|
||||
newGroupBy.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
setGroupBy(groupBy);
|
||||
|
||||
setGroupBy(newGroupBy);
|
||||
setGroupBySearchValue('');
|
||||
},
|
||||
[groupByFiltersData, setGroupBy],
|
||||
[groupByFiltersData, setGroupBy, allAvailableGroupByOptions],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupByFiltersData?.payload) {
|
||||
// Update dropdown options
|
||||
setGroupByOptions(
|
||||
groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({
|
||||
value: filter.key,
|
||||
label: filter.key,
|
||||
})) || [],
|
||||
);
|
||||
|
||||
// Cache all available options to preserve selected values using functional update
|
||||
// to avoid dependency on allAvailableGroupByOptions
|
||||
setAllAvailableGroupByOptions((prevOptions) => {
|
||||
const newOptions = { ...prevOptions };
|
||||
groupByFiltersData?.payload?.attributeKeys?.forEach((filter) => {
|
||||
newOptions[filter.key] = filter;
|
||||
});
|
||||
return newOptions;
|
||||
});
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
}, [groupByFiltersData]); // Only depends on groupByFiltersData now
|
||||
|
||||
// Cache existing selected options on component mount
|
||||
useEffect(() => {
|
||||
if (groupBy && groupBy.length > 0) {
|
||||
setAllAvailableGroupByOptions((prevOptions) => {
|
||||
const newOptions = { ...prevOptions };
|
||||
groupBy.forEach((option) => {
|
||||
newOptions[option.key] = option;
|
||||
});
|
||||
return newOptions;
|
||||
});
|
||||
}
|
||||
}, [groupBy]); // Removed allAvailableGroupByOptions from dependencies
|
||||
|
||||
const currentQuery = initialQueriesMap[DataSource.TRACES];
|
||||
|
||||
@ -168,6 +205,7 @@ function AllEndPoints({
|
||||
placeholder="Search for attribute"
|
||||
options={groupByOptions}
|
||||
onChange={handleGroupByChange}
|
||||
onSearch={(value: string): void => setGroupBySearchValue(value)}
|
||||
/>{' '}
|
||||
</div>
|
||||
<div className="endpoints-table-container">
|
||||
|
@ -229,7 +229,7 @@ function DomainDetails({
|
||||
<TopErrors
|
||||
domainName={domainData.domainName}
|
||||
timeRange={modalTimeRange}
|
||||
handleTimeChange={handleTimeChange}
|
||||
initialFilters={domainListFilters}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -12,17 +12,14 @@ import {
|
||||
getTopErrorsQueryPayload,
|
||||
TopErrorsResponseRow,
|
||||
} from 'container/ApiMonitoring/utils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { Info } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import EndPointsDropDown from './components/EndPointsDropDown';
|
||||
@ -32,17 +29,14 @@ import { SPAN_ATTRIBUTES } from './constants';
|
||||
function TopErrors({
|
||||
domainName,
|
||||
timeRange,
|
||||
handleTimeChange,
|
||||
initialFilters,
|
||||
}: {
|
||||
domainName: string;
|
||||
timeRange: {
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
handleTimeChange: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
initialFilters: IBuilderQuery['filters'];
|
||||
}): JSX.Element {
|
||||
const { startTime: minTime, endTime: maxTime } = timeRange;
|
||||
|
||||
@ -65,11 +59,12 @@ function TopErrors({
|
||||
op: '=',
|
||||
value: endPointName,
|
||||
},
|
||||
...initialFilters.items,
|
||||
]
|
||||
: [],
|
||||
: [...initialFilters.items],
|
||||
op: 'AND',
|
||||
}),
|
||||
[domainName, endPointName, minTime, maxTime],
|
||||
[domainName, endPointName, minTime, maxTime, initialFilters],
|
||||
);
|
||||
|
||||
// Since only one query here
|
||||
@ -135,10 +130,6 @@ function TopErrors({
|
||||
[endPointDropDownDataQueries],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
handleTimeChange('6h');
|
||||
}, [handleTimeChange]);
|
||||
|
||||
const navigateToExplorer = useNavigateToExplorer();
|
||||
|
||||
if (isError) {
|
||||
|
@ -146,13 +146,13 @@ function DomainMetrics({
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(
|
||||
Number(formattedDomainMetricsData.errorRate).toFixed(1),
|
||||
Number(formattedDomainMetricsData.errorRate).toFixed(2),
|
||||
)}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number(
|
||||
Number(formattedDomainMetricsData.errorRate).toFixed(1),
|
||||
Number(formattedDomainMetricsData.errorRate).toFixed(2),
|
||||
);
|
||||
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
||||
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
||||
|
@ -90,12 +90,12 @@ function EndPointMetrics({
|
||||
<Tooltip title={metricsData?.errorRate}>
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(1))}
|
||||
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(2))}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number(
|
||||
Number(metricsData?.errorRate ?? 0).toFixed(1),
|
||||
Number(metricsData?.errorRate ?? 0).toFixed(2),
|
||||
);
|
||||
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
||||
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
||||
|
@ -14,6 +14,7 @@ export const VIEW_TYPES = {
|
||||
export const SPAN_ATTRIBUTES = {
|
||||
URL_PATH: 'http.url',
|
||||
STATUS_CODE: 'status_code',
|
||||
RESPONSE_STATUS_CODE: 'response_status_code',
|
||||
SERVER_NAME: 'net.peer.name',
|
||||
SERVER_PORT: 'net.peer.port',
|
||||
} as const;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
||||
import {
|
||||
getAllEndpointsWidgetData,
|
||||
getGroupByFiltersFromGroupByValues,
|
||||
@ -129,6 +129,11 @@ describe('AllEndPoints', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Add cleanup after each test
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('renders component correctly', () => {
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
render(<AllEndPoints {...mockProps} />);
|
||||
|
@ -61,7 +61,10 @@ describe('TopErrors', () => {
|
||||
startTime: 1000000000,
|
||||
endTime: 1000010000,
|
||||
},
|
||||
handleTimeChange: jest.fn(),
|
||||
initialFilters: {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
};
|
||||
|
||||
// Setup basic mocks
|
||||
@ -223,12 +226,6 @@ describe('TopErrors', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('calls handleTimeChange with 6h on mount', () => {
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
render(<TopErrors {...mockProps} />);
|
||||
expect(mockProps.handleTimeChange).toHaveBeenCalledWith('6h');
|
||||
});
|
||||
|
||||
it('renders error state when isError is true', () => {
|
||||
// Mock useQueries to return isError: true
|
||||
(useQueries as jest.Mock).mockImplementationOnce(() => [
|
||||
|
@ -208,17 +208,16 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
|
||||
align: 'right',
|
||||
className: `column`,
|
||||
render: (errorRate: number | string): React.ReactNode => {
|
||||
if (errorRate === 'n/a' || errorRate === '-') {
|
||||
return '-';
|
||||
}
|
||||
const errorRateValue =
|
||||
errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate;
|
||||
return (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number((errorRate as number).toFixed(1))}
|
||||
percent={Number((errorRateValue as number).toFixed(2))}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number((errorRate as number).toFixed(1));
|
||||
const errorRatePercent = Number((errorRateValue as number).toFixed(2));
|
||||
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
||||
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
||||
return Color.BG_FOREST_500;
|
||||
@ -941,18 +940,6 @@ export const getTopErrorsQueryPayload = (
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
},
|
||||
{
|
||||
id: '75d65388',
|
||||
key: {
|
||||
key: 'status_message',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: 'exists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: 'b1af6bdb',
|
||||
key: {
|
||||
@ -965,6 +952,18 @@ export const getTopErrorsQueryPayload = (
|
||||
op: 'exists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '75d65388',
|
||||
key: {
|
||||
key: 'status_message',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: 'exists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '4872bf91',
|
||||
key: {
|
||||
@ -977,6 +976,18 @@ export const getTopErrorsQueryPayload = (
|
||||
op: '=',
|
||||
value: domainName,
|
||||
},
|
||||
{
|
||||
id: 'ab4c885d',
|
||||
key: {
|
||||
key: 'has_error',
|
||||
dataType: DataTypes.bool,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: true,
|
||||
},
|
||||
...filters.items,
|
||||
],
|
||||
},
|
||||
@ -1000,11 +1011,12 @@ export const getTopErrorsQueryPayload = (
|
||||
isJSON: false,
|
||||
},
|
||||
{
|
||||
key: 'status_code',
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
id: 'response_status_code--string----true',
|
||||
},
|
||||
{
|
||||
key: 'status_message',
|
||||
@ -1327,7 +1339,7 @@ export const formatEndPointsDataForTable = (
|
||||
export interface TopErrorsResponseRow {
|
||||
metric: {
|
||||
[SPAN_ATTRIBUTES.URL_PATH]: string;
|
||||
[SPAN_ATTRIBUTES.STATUS_CODE]: string;
|
||||
[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE]: string;
|
||||
status_message: string;
|
||||
};
|
||||
values: [number, string][];
|
||||
@ -1356,10 +1368,10 @@ export const formatTopErrorsDataForTable = (
|
||||
? '-'
|
||||
: row.metric[SPAN_ATTRIBUTES.URL_PATH],
|
||||
statusCode:
|
||||
row.metric[SPAN_ATTRIBUTES.STATUS_CODE] === 'n/a' ||
|
||||
row.metric[SPAN_ATTRIBUTES.STATUS_CODE] === undefined
|
||||
row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE] === 'n/a' ||
|
||||
row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE] === undefined
|
||||
? '-'
|
||||
: row.metric[SPAN_ATTRIBUTES.STATUS_CODE],
|
||||
: row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE],
|
||||
statusMessage:
|
||||
row.metric.status_message === 'n/a' ||
|
||||
row.metric.status_message === undefined
|
||||
@ -2005,7 +2017,7 @@ export const getEndPointDetailsQueryPayload = (
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: 'api.github.com',
|
||||
value: domainName,
|
||||
},
|
||||
{
|
||||
id: '212678b9',
|
||||
@ -3057,28 +3069,28 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
|
||||
render: (
|
||||
errorPercentage: number | string,
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
): React.ReactNode => (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(
|
||||
((errorPercentage === 'n/a' || errorPercentage === '-'
|
||||
? 0
|
||||
: errorPercentage) as number).toFixed(1),
|
||||
)}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
strokeColor={((): // eslint-disable-next-line sonarjs/no-identical-functions
|
||||
string => {
|
||||
const errorPercentagePercent = Number(
|
||||
(errorPercentage as number).toFixed(1),
|
||||
);
|
||||
if (errorPercentagePercent >= 90) return Color.BG_SAKURA_500;
|
||||
if (errorPercentagePercent >= 60) return Color.BG_AMBER_500;
|
||||
return Color.BG_FOREST_500;
|
||||
})()}
|
||||
className="progress-bar error-rate"
|
||||
/>
|
||||
),
|
||||
): React.ReactNode => {
|
||||
const errorPercentageValue =
|
||||
errorPercentage === 'n/a' || errorPercentage === '-' ? 0 : errorPercentage;
|
||||
return (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number((errorPercentageValue as number).toFixed(2))}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
strokeColor={((): // eslint-disable-next-line sonarjs/no-identical-functions
|
||||
string => {
|
||||
const errorPercentagePercent = Number(
|
||||
(errorPercentageValue as number).toFixed(2),
|
||||
);
|
||||
if (errorPercentagePercent >= 90) return Color.BG_SAKURA_500;
|
||||
if (errorPercentagePercent >= 60) return Color.BG_AMBER_500;
|
||||
return Color.BG_FOREST_500;
|
||||
})()}
|
||||
className="progress-bar error-rate"
|
||||
/>
|
||||
);
|
||||
},
|
||||
sorter: (a: DependentServicesData, b: DependentServicesData): number => {
|
||||
const errorPercentageA =
|
||||
a.errorPercentage === '-' || a.errorPercentage === 'n/a'
|
||||
@ -3658,12 +3670,9 @@ export const getAllEndpointsWidgetData = (
|
||||
|
||||
widget.renderColumnCell = {
|
||||
[SPAN_ATTRIBUTES.URL_PATH]: (url: any): ReactNode => {
|
||||
const { endpoint, port } = extractPortAndEndpoint(url);
|
||||
const { endpoint } = extractPortAndEndpoint(url);
|
||||
return (
|
||||
<span>
|
||||
{port !== '-' && port !== 'n/a' ? `:${port}` : ''}
|
||||
{endpoint === 'n/a' || url === undefined ? '-' : endpoint}
|
||||
</span>
|
||||
<span>{endpoint === 'n/a' || url === undefined ? '-' : endpoint}</span>
|
||||
);
|
||||
},
|
||||
A: (numOfCalls: any): ReactNode => (
|
||||
@ -3695,7 +3704,7 @@ export const getAllEndpointsWidgetData = (
|
||||
percent={Number(
|
||||
((errorRate === 'n/a' || errorRate === '-'
|
||||
? 0
|
||||
: errorRate) as number).toFixed(1),
|
||||
: errorRate) as number).toFixed(2),
|
||||
)}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
@ -3704,7 +3713,7 @@ export const getAllEndpointsWidgetData = (
|
||||
const errorRatePercent = Number(
|
||||
((errorRate === 'n/a' || errorRate === '-'
|
||||
? 0
|
||||
: errorRate) as number).toFixed(1),
|
||||
: errorRate) as number).toFixed(2),
|
||||
);
|
||||
if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
|
||||
if (errorRatePercent >= 60) return Color.BG_AMBER_500;
|
||||
|
Loading…
x
Reference in New Issue
Block a user