feat: handle unkown metric in panel query (#8083)

* feat: handle unkown metric in panel query

* feat: added handling with type empty and key present or not

* feat: added test cases

* feat: added comment to better explain the logic

* feat: fixed operator list for unkown metric

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
SagarRajput-7
2025-06-05 16:53:25 +05:30
committed by GitHub
parent 90770b90bd
commit d60c9ab36b
4 changed files with 251 additions and 8 deletions

View File

@@ -425,3 +425,79 @@ export const metricsEmptyTimeAggregateOperatorOptions: SelectOption<
string,
string
>[] = [];
export const metricsUnknownTimeAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.MAX,
label: 'Max',
},
{
value: MetricAggregateOperator.MIN,
label: 'Min',
},
{
value: MetricAggregateOperator.SUM,
label: 'Sum',
},
{
value: MetricAggregateOperator.AVG,
label: 'Avg',
},
{
value: MetricAggregateOperator.COUNT,
label: 'Count',
},
{
value: MetricAggregateOperator.RATE,
label: 'Rate',
},
{
value: MetricAggregateOperator.INCREASE,
label: 'Increase',
},
];
export const metricsUnknownSpaceAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.SUM,
label: 'Sum',
},
{
value: MetricAggregateOperator.AVG,
label: 'Avg',
},
{
value: MetricAggregateOperator.MIN,
label: 'Min',
},
{
value: MetricAggregateOperator.MAX,
label: 'Max',
},
{
value: MetricAggregateOperator.P50,
label: 'P50',
},
{
value: MetricAggregateOperator.P75,
label: 'P75',
},
{
value: MetricAggregateOperator.P90,
label: 'P90',
},
{
value: MetricAggregateOperator.P95,
label: 'P95',
},
{
value: MetricAggregateOperator.P99,
label: 'P99',
},
];

View File

@@ -0,0 +1,135 @@
import { act, renderHook } from '@testing-library/react';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { ATTRIBUTE_TYPES } from 'constants/queryBuilder';
import {
BaseAutocompleteData,
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
import { useQueryBuilder } from '../useQueryBuilder';
import { useQueryOperations } from '../useQueryBuilderOperations';
// Mock the useQueryBuilder hook
jest.mock('../useQueryBuilder', () => ({
useQueryBuilder: jest.fn(),
}));
describe('useQueryBuilderOperations - Empty Aggregate Attribute Type', () => {
const mockHandleSetQueryData = jest.fn();
const mockHandleSetFormulaData = jest.fn();
const mockRemoveQueryBuilderEntityByIndex = jest.fn();
const mockSetLastUsedQuery = jest.fn();
const mockRedirectWithQueryBuilderData = jest.fn();
const defaultMockQuery: IBuilderQuery = {
dataSource: DataSource.METRICS,
aggregateOperator: MetricAggregateOperator.AVG,
aggregateAttribute: {
key: 'test_metric',
dataType: DataTypes.Float64,
type: ATTRIBUTE_TYPES.GAUGE,
} as BaseAutocompleteData,
timeAggregation: MetricAggregateOperator.AVG,
spaceAggregation: '',
having: [],
limit: null,
queryName: 'test_query',
functions: [],
filters: {
items: [],
op: 'AND',
},
groupBy: [],
orderBy: [],
stepInterval: 60,
expression: '',
disabled: false,
reduceTo: 'avg',
legend: '',
};
const setupMockQueryBuilder = (): void => {
(useQueryBuilder as jest.Mock).mockReturnValue({
handleSetQueryData: mockHandleSetQueryData,
handleSetFormulaData: mockHandleSetFormulaData,
removeQueryBuilderEntityByIndex: mockRemoveQueryBuilderEntityByIndex,
setLastUsedQuery: mockSetLastUsedQuery,
redirectWithQueryBuilderData: mockRedirectWithQueryBuilderData,
panelType: 'time_series',
currentQuery: {
builder: {
queryData: [defaultMockQuery, defaultMockQuery],
},
},
});
};
const renderHookWithProps = (
props = {},
): { current: ReturnType<typeof useQueryOperations> } => {
const { result } = renderHook(() =>
useQueryOperations({
query: defaultMockQuery,
index: 0,
entityVersion: ENTITY_VERSION_V4,
...props,
}),
);
return result;
};
beforeEach(() => {
jest.clearAllMocks();
setupMockQueryBuilder();
});
describe('handleChangeAggregatorAttribute', () => {
it('should set AVG operators when type is empty but key is present - unkown metric', () => {
const result = renderHookWithProps();
const newAttribute: BaseAutocompleteData = {
key: 'new_metric',
dataType: DataTypes.Float64,
type: '',
};
act(() => {
result.current.handleChangeAggregatorAttribute(newAttribute);
});
expect(mockHandleSetQueryData).toHaveBeenCalledWith(
0,
expect.objectContaining({
aggregateAttribute: newAttribute,
aggregateOperator: MetricAggregateOperator.AVG,
timeAggregation: MetricAggregateOperator.AVG,
spaceAggregation: MetricAggregateOperator.AVG,
}),
);
});
it('should set COUNT/RATE/SUM operators when both type and key are empty', () => {
const result = renderHookWithProps();
const newAttribute: BaseAutocompleteData = {
key: '',
dataType: DataTypes.Float64,
type: '',
};
act(() => {
result.current.handleChangeAggregatorAttribute(newAttribute);
});
expect(mockHandleSetQueryData).toHaveBeenCalledWith(
0,
expect.objectContaining({
aggregateAttribute: newAttribute,
aggregateOperator: MetricAggregateOperator.COUNT,
timeAggregation: MetricAggregateOperator.RATE,
spaceAggregation: MetricAggregateOperator.SUM,
}),
);
});
});
});

View File

@@ -12,6 +12,8 @@ import {
metricsGaugeSpaceAggregateOperatorOptions,
metricsHistogramSpaceAggregateOperatorOptions,
metricsSumSpaceAggregateOperatorOptions,
metricsUnknownSpaceAggregateOperatorOptions,
metricsUnknownTimeAggregateOperatorOptions,
} from 'constants/queryBuilderOperators';
import {
listViewInitialLogQuery,
@@ -21,6 +23,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getMetricsOperatorsByAttributeType } from 'lib/newQueryBuilder/getMetricsOperatorsByAttributeType';
import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
import { isEmpty } from 'lodash-es';
import { useCallback, useEffect, useState } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
@@ -145,12 +148,18 @@ export const useQueryOperations: UseQueryOperations = ({
const handleMetricAggregateAtributeTypes = useCallback(
(aggregateAttribute: BaseAutocompleteData): any => {
const newOperators = getMetricsOperatorsByAttributeType({
dataSource: DataSource.METRICS,
panelType: panelType || PANEL_TYPES.TIME_SERIES,
aggregateAttributeType:
(aggregateAttribute.type as ATTRIBUTE_TYPES) || ATTRIBUTE_TYPES.GAUGE,
});
// operators for unknown metric
const isUnknownMetric =
isEmpty(aggregateAttribute.type) && !isEmpty(aggregateAttribute.key);
const newOperators = isUnknownMetric
? metricsUnknownTimeAggregateOperatorOptions
: getMetricsOperatorsByAttributeType({
dataSource: DataSource.METRICS,
panelType: panelType || PANEL_TYPES.TIME_SERIES,
aggregateAttributeType:
(aggregateAttribute.type as ATTRIBUTE_TYPES) || ATTRIBUTE_TYPES.GAUGE,
});
switch (aggregateAttribute.type) {
case ATTRIBUTE_TYPES.SUM:
@@ -168,7 +177,7 @@ export const useQueryOperations: UseQueryOperations = ({
setSpaceAggregationOptions(metricsHistogramSpaceAggregateOperatorOptions);
break;
default:
setSpaceAggregationOptions(metricsGaugeSpaceAggregateOperatorOptions);
setSpaceAggregationOptions(metricsUnknownSpaceAggregateOperatorOptions);
break;
}
@@ -202,6 +211,21 @@ export const useQueryOperations: UseQueryOperations = ({
}
newQuery.spaceAggregation = '';
// Handled query with unknown metric to avoid 400 and 500 errors
// With metric value typed and not available then - time - 'avg', space - 'avg'
// If not typed - time - 'rate', space - 'sum', op - 'count'
if (isEmpty(newQuery.aggregateAttribute.type)) {
if (!isEmpty(newQuery.aggregateAttribute.key)) {
newQuery.aggregateOperator = MetricAggregateOperator.AVG;
newQuery.timeAggregation = MetricAggregateOperator.AVG;
newQuery.spaceAggregation = MetricAggregateOperator.AVG;
} else {
newQuery.aggregateOperator = MetricAggregateOperator.COUNT;
newQuery.timeAggregation = MetricAggregateOperator.RATE;
newQuery.spaceAggregation = MetricAggregateOperator.SUM;
}
}
}
handleSetQueryData(index, newQuery);

View File

@@ -3,7 +3,11 @@ import {
metricsOperatorsByType,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { metricsEmptyTimeAggregateOperatorOptions } from 'constants/queryBuilderOperators';
import {
metricsEmptyTimeAggregateOperatorOptions,
metricsUnknownTimeAggregateOperatorOptions,
} from 'constants/queryBuilderOperators';
import { isEmpty } from 'lodash-es';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
@@ -27,5 +31,9 @@ export const getMetricsOperatorsByAttributeType = ({
}
}
if (dataSource === DataSource.METRICS && isEmpty(aggregateAttributeType)) {
return metricsUnknownTimeAggregateOperatorOptions;
}
return metricsEmptyTimeAggregateOperatorOptions;
};