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: { metric: {
[SPAN_ATTRIBUTES.URL_PATH]: '/api/test', [SPAN_ATTRIBUTES.URL_PATH]: '/api/test',
[SPAN_ATTRIBUTES.STATUS_CODE]: '500', [SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE]: '500',
status_message: 'Internal Server Error', status_message: 'Internal Server Error',
}, },
values: [[1000000100, '10']], values: [[1000000100, '10']],

View File

@ -35,6 +35,11 @@ function AllEndPoints({
initialFilters: IBuilderQuery['filters']; initialFilters: IBuilderQuery['filters'];
setInitialFiltersEndPointStats: (filters: IBuilderQuery['filters']) => void; setInitialFiltersEndPointStats: (filters: IBuilderQuery['filters']) => void;
}): JSX.Element { }): JSX.Element {
const [groupBySearchValue, setGroupBySearchValue] = useState<string>('');
const [allAvailableGroupByOptions, setAllAvailableGroupByOptions] = useState<{
[key: string]: any;
}>({});
const { const {
data: groupByFiltersData, data: groupByFiltersData,
isLoading: isLoadingGroupByFilters, isLoading: isLoadingGroupByFilters,
@ -42,7 +47,7 @@ function AllEndPoints({
dataSource: DataSource.TRACES, dataSource: DataSource.TRACES,
aggregateAttribute: '', aggregateAttribute: '',
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: groupBySearchValue,
tagType: '', tagType: '',
}); });
@ -52,34 +57,66 @@ function AllEndPoints({
const handleGroupByChange = useCallback( const handleGroupByChange = useCallback(
(value: IBuilderQuery['groupBy']) => { (value: IBuilderQuery['groupBy']) => {
const groupBy = []; const newGroupBy = [];
for (let index = 0; index < value.length; index++) { for (let index = 0; index < value.length; index++) {
const element = (value[index] as unknown) as string; const element = (value[index] as unknown) as string;
const key = groupByFiltersData?.payload?.attributeKeys?.find( // Check if the key exists in our cached options first
(key) => key.key === element, 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) { if (key) {
groupBy.push(key); newGroupBy.push(key);
}
} }
} }
setGroupBy(groupBy);
setGroupBy(newGroupBy);
setGroupBySearchValue('');
}, },
[groupByFiltersData, setGroupBy], [groupByFiltersData, setGroupBy, allAvailableGroupByOptions],
); );
useEffect(() => { useEffect(() => {
if (groupByFiltersData?.payload) { if (groupByFiltersData?.payload) {
// Update dropdown options
setGroupByOptions( setGroupByOptions(
groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({ groupByFiltersData?.payload?.attributeKeys?.map((filter) => ({
value: filter.key, value: filter.key,
label: 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]; const currentQuery = initialQueriesMap[DataSource.TRACES];
@ -168,6 +205,7 @@ function AllEndPoints({
placeholder="Search for attribute" placeholder="Search for attribute"
options={groupByOptions} options={groupByOptions}
onChange={handleGroupByChange} onChange={handleGroupByChange}
onSearch={(value: string): void => setGroupBySearchValue(value)}
/>{' '} />{' '}
</div> </div>
<div className="endpoints-table-container"> <div className="endpoints-table-container">

View File

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

View File

@ -12,17 +12,14 @@ import {
getTopErrorsQueryPayload, getTopErrorsQueryPayload,
TopErrorsResponseRow, TopErrorsResponseRow,
} from 'container/ApiMonitoring/utils'; } from 'container/ApiMonitoring/utils';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/config';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { Info } from 'lucide-react'; import { Info } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useQueries } from 'react-query'; import { useQueries } from 'react-query';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import EndPointsDropDown from './components/EndPointsDropDown'; import EndPointsDropDown from './components/EndPointsDropDown';
@ -32,17 +29,14 @@ import { SPAN_ATTRIBUTES } from './constants';
function TopErrors({ function TopErrors({
domainName, domainName,
timeRange, timeRange,
handleTimeChange, initialFilters,
}: { }: {
domainName: string; domainName: string;
timeRange: { timeRange: {
startTime: number; startTime: number;
endTime: number; endTime: number;
}; };
handleTimeChange: ( initialFilters: IBuilderQuery['filters'];
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
}): JSX.Element { }): JSX.Element {
const { startTime: minTime, endTime: maxTime } = timeRange; const { startTime: minTime, endTime: maxTime } = timeRange;
@ -65,11 +59,12 @@ function TopErrors({
op: '=', op: '=',
value: endPointName, value: endPointName,
}, },
...initialFilters.items,
] ]
: [], : [...initialFilters.items],
op: 'AND', op: 'AND',
}), }),
[domainName, endPointName, minTime, maxTime], [domainName, endPointName, minTime, maxTime, initialFilters],
); );
// Since only one query here // Since only one query here
@ -135,10 +130,6 @@ function TopErrors({
[endPointDropDownDataQueries], [endPointDropDownDataQueries],
); );
useEffect(() => {
handleTimeChange('6h');
}, [handleTimeChange]);
const navigateToExplorer = useNavigateToExplorer(); const navigateToExplorer = useNavigateToExplorer();
if (isError) { if (isError) {

View File

@ -146,13 +146,13 @@ function DomainMetrics({
<Progress <Progress
status="active" status="active"
percent={Number( percent={Number(
Number(formattedDomainMetricsData.errorRate).toFixed(1), Number(formattedDomainMetricsData.errorRate).toFixed(2),
)} )}
strokeLinecap="butt" strokeLinecap="butt"
size="small" size="small"
strokeColor={((): string => { strokeColor={((): string => {
const errorRatePercent = Number( const errorRatePercent = Number(
Number(formattedDomainMetricsData.errorRate).toFixed(1), Number(formattedDomainMetricsData.errorRate).toFixed(2),
); );
if (errorRatePercent >= 90) return Color.BG_SAKURA_500; if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
if (errorRatePercent >= 60) return Color.BG_AMBER_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500;

View File

@ -90,12 +90,12 @@ function EndPointMetrics({
<Tooltip title={metricsData?.errorRate}> <Tooltip title={metricsData?.errorRate}>
<Progress <Progress
status="active" status="active"
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(1))} percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(2))}
strokeLinecap="butt" strokeLinecap="butt"
size="small" size="small"
strokeColor={((): string => { strokeColor={((): string => {
const errorRatePercent = Number( 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 >= 90) return Color.BG_SAKURA_500;
if (errorRatePercent >= 60) return Color.BG_AMBER_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500;

View File

@ -14,6 +14,7 @@ export const VIEW_TYPES = {
export const SPAN_ATTRIBUTES = { export const SPAN_ATTRIBUTES = {
URL_PATH: 'http.url', URL_PATH: 'http.url',
STATUS_CODE: 'status_code', STATUS_CODE: 'status_code',
RESPONSE_STATUS_CODE: 'response_status_code',
SERVER_NAME: 'net.peer.name', SERVER_NAME: 'net.peer.name',
SERVER_PORT: 'net.peer.port', SERVER_PORT: 'net.peer.port',
} as const; } 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 { import {
getAllEndpointsWidgetData, getAllEndpointsWidgetData,
getGroupByFiltersFromGroupByValues, getGroupByFiltersFromGroupByValues,
@ -129,6 +129,11 @@ describe('AllEndPoints', () => {
}); });
}); });
// Add cleanup after each test
afterEach(() => {
cleanup();
});
it('renders component correctly', () => { it('renders component correctly', () => {
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
render(<AllEndPoints {...mockProps} />); render(<AllEndPoints {...mockProps} />);

View File

@ -61,7 +61,10 @@ describe('TopErrors', () => {
startTime: 1000000000, startTime: 1000000000,
endTime: 1000010000, endTime: 1000010000,
}, },
handleTimeChange: jest.fn(), initialFilters: {
items: [],
op: 'AND',
},
}; };
// Setup basic mocks // 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', () => { it('renders error state when isError is true', () => {
// Mock useQueries to return isError: true // Mock useQueries to return isError: true
(useQueries as jest.Mock).mockImplementationOnce(() => [ (useQueries as jest.Mock).mockImplementationOnce(() => [

View File

@ -208,17 +208,16 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
align: 'right', align: 'right',
className: `column`, className: `column`,
render: (errorRate: number | string): React.ReactNode => { render: (errorRate: number | string): React.ReactNode => {
if (errorRate === 'n/a' || errorRate === '-') { const errorRateValue =
return '-'; errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate;
}
return ( return (
<Progress <Progress
status="active" status="active"
percent={Number((errorRate as number).toFixed(1))} percent={Number((errorRateValue as number).toFixed(2))}
strokeLinecap="butt" strokeLinecap="butt"
size="small" size="small"
strokeColor={((): string => { 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 >= 90) return Color.BG_SAKURA_500;
if (errorRatePercent >= 60) return Color.BG_AMBER_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500;
return Color.BG_FOREST_500; return Color.BG_FOREST_500;
@ -941,18 +940,6 @@ export const getTopErrorsQueryPayload = (
op: '=', op: '=',
value: 'Client', value: 'Client',
}, },
{
id: '75d65388',
key: {
key: 'status_message',
dataType: DataTypes.String,
type: '',
isColumn: true,
isJSON: false,
},
op: 'exists',
value: '',
},
{ {
id: 'b1af6bdb', id: 'b1af6bdb',
key: { key: {
@ -965,6 +952,18 @@ export const getTopErrorsQueryPayload = (
op: 'exists', op: 'exists',
value: '', value: '',
}, },
{
id: '75d65388',
key: {
key: 'status_message',
dataType: DataTypes.String,
type: '',
isColumn: true,
isJSON: false,
},
op: 'exists',
value: '',
},
{ {
id: '4872bf91', id: '4872bf91',
key: { key: {
@ -977,6 +976,18 @@ export const getTopErrorsQueryPayload = (
op: '=', op: '=',
value: domainName, value: domainName,
}, },
{
id: 'ab4c885d',
key: {
key: 'has_error',
dataType: DataTypes.bool,
type: '',
isColumn: true,
isJSON: false,
},
op: '=',
value: true,
},
...filters.items, ...filters.items,
], ],
}, },
@ -1000,11 +1011,12 @@ export const getTopErrorsQueryPayload = (
isJSON: false, isJSON: false,
}, },
{ {
key: 'status_code', dataType: DataTypes.String,
dataType: DataTypes.Float64,
type: '',
isColumn: true, isColumn: true,
isJSON: false, isJSON: false,
key: 'response_status_code',
type: '',
id: 'response_status_code--string----true',
}, },
{ {
key: 'status_message', key: 'status_message',
@ -1327,7 +1339,7 @@ export const formatEndPointsDataForTable = (
export interface TopErrorsResponseRow { export interface TopErrorsResponseRow {
metric: { metric: {
[SPAN_ATTRIBUTES.URL_PATH]: string; [SPAN_ATTRIBUTES.URL_PATH]: string;
[SPAN_ATTRIBUTES.STATUS_CODE]: string; [SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE]: string;
status_message: string; status_message: string;
}; };
values: [number, string][]; values: [number, string][];
@ -1356,10 +1368,10 @@ export const formatTopErrorsDataForTable = (
? '-' ? '-'
: row.metric[SPAN_ATTRIBUTES.URL_PATH], : row.metric[SPAN_ATTRIBUTES.URL_PATH],
statusCode: statusCode:
row.metric[SPAN_ATTRIBUTES.STATUS_CODE] === 'n/a' || row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE] === 'n/a' ||
row.metric[SPAN_ATTRIBUTES.STATUS_CODE] === undefined row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE] === undefined
? '-' ? '-'
: row.metric[SPAN_ATTRIBUTES.STATUS_CODE], : row.metric[SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE],
statusMessage: statusMessage:
row.metric.status_message === 'n/a' || row.metric.status_message === 'n/a' ||
row.metric.status_message === undefined row.metric.status_message === undefined
@ -2005,7 +2017,7 @@ export const getEndPointDetailsQueryPayload = (
type: 'tag', type: 'tag',
}, },
op: '=', op: '=',
value: 'api.github.com', value: domainName,
}, },
{ {
id: '212678b9', id: '212678b9',
@ -3057,28 +3069,28 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
render: ( render: (
errorPercentage: number | string, errorPercentage: number | string,
// eslint-disable-next-line sonarjs/no-identical-functions // eslint-disable-next-line sonarjs/no-identical-functions
): React.ReactNode => ( ): React.ReactNode => {
<Progress const errorPercentageValue =
status="active" errorPercentage === 'n/a' || errorPercentage === '-' ? 0 : errorPercentage;
percent={Number( return (
((errorPercentage === 'n/a' || errorPercentage === '-' <Progress
? 0 status="active"
: errorPercentage) as number).toFixed(1), percent={Number((errorPercentageValue as number).toFixed(2))}
)} strokeLinecap="butt"
strokeLinecap="butt" size="small"
size="small" strokeColor={((): // eslint-disable-next-line sonarjs/no-identical-functions
strokeColor={((): // eslint-disable-next-line sonarjs/no-identical-functions string => {
string => { const errorPercentagePercent = Number(
const errorPercentagePercent = Number( (errorPercentageValue as number).toFixed(2),
(errorPercentage as number).toFixed(1), );
); if (errorPercentagePercent >= 90) return Color.BG_SAKURA_500;
if (errorPercentagePercent >= 90) return Color.BG_SAKURA_500; if (errorPercentagePercent >= 60) return Color.BG_AMBER_500;
if (errorPercentagePercent >= 60) return Color.BG_AMBER_500; return Color.BG_FOREST_500;
return Color.BG_FOREST_500; })()}
})()} className="progress-bar error-rate"
className="progress-bar error-rate" />
/> );
), },
sorter: (a: DependentServicesData, b: DependentServicesData): number => { sorter: (a: DependentServicesData, b: DependentServicesData): number => {
const errorPercentageA = const errorPercentageA =
a.errorPercentage === '-' || a.errorPercentage === 'n/a' a.errorPercentage === '-' || a.errorPercentage === 'n/a'
@ -3658,12 +3670,9 @@ export const getAllEndpointsWidgetData = (
widget.renderColumnCell = { widget.renderColumnCell = {
[SPAN_ATTRIBUTES.URL_PATH]: (url: any): ReactNode => { [SPAN_ATTRIBUTES.URL_PATH]: (url: any): ReactNode => {
const { endpoint, port } = extractPortAndEndpoint(url); const { endpoint } = extractPortAndEndpoint(url);
return ( return (
<span> <span>{endpoint === 'n/a' || url === undefined ? '-' : endpoint}</span>
{port !== '-' && port !== 'n/a' ? `:${port}` : ''}
{endpoint === 'n/a' || url === undefined ? '-' : endpoint}
</span>
); );
}, },
A: (numOfCalls: any): ReactNode => ( A: (numOfCalls: any): ReactNode => (
@ -3695,7 +3704,7 @@ export const getAllEndpointsWidgetData = (
percent={Number( percent={Number(
((errorRate === 'n/a' || errorRate === '-' ((errorRate === 'n/a' || errorRate === '-'
? 0 ? 0
: errorRate) as number).toFixed(1), : errorRate) as number).toFixed(2),
)} )}
strokeLinecap="butt" strokeLinecap="butt"
size="small" size="small"
@ -3704,7 +3713,7 @@ export const getAllEndpointsWidgetData = (
const errorRatePercent = Number( const errorRatePercent = Number(
((errorRate === 'n/a' || errorRate === '-' ((errorRate === 'n/a' || errorRate === '-'
? 0 ? 0
: errorRate) as number).toFixed(1), : errorRate) as number).toFixed(2),
); );
if (errorRatePercent >= 90) return Color.BG_SAKURA_500; if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
if (errorRatePercent >= 60) return Color.BG_AMBER_500; if (errorRatePercent >= 60) return Color.BG_AMBER_500;