fix: api monitoring cosmetic changes (#7771)

fix: minor changes
This commit is contained in:
Sahil Khan 2025-04-30 02:56:07 +05:30 committed by GitHub
parent cf451d335c
commit 75d86cea60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 135 additions and 94 deletions

View File

@ -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']],

View File

@ -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">

View File

@ -229,7 +229,7 @@ function DomainDetails({
<TopErrors
domainName={domainData.domainName}
timeRange={modalTimeRange}
handleTimeChange={handleTimeChange}
initialFilters={domainListFilters}
/>
)}
</>

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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} />);

View File

@ -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(() => [

View File

@ -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;