fix: log details filters use data types from log data response as primary data type (#8278)

* fix: log details filters use data types from log data response as primary data type

* chore: added test cases

* test: add comprehensive unit tests for chooseAutocompleteFromCustomValue function

* fix: added datatypes to util and test cases

* fix: added new tests
This commit is contained in:
Sahil Khan 2025-06-22 16:28:43 +05:30 committed by GitHub
parent bbb21f608f
commit b11a4c0c21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 359 additions and 8 deletions

View File

@ -5,17 +5,19 @@ import cx from 'classnames';
import { OPERATORS } from 'constants/queryBuilder';
import { FontSize } from 'container/OptionsMenu/types';
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
function AddToQueryHOC({
fieldKey,
fieldValue,
onAddToQuery,
fontSize,
dataType = DataTypes.EMPTY,
children,
}: AddToQueryHOCProps): JSX.Element {
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
event.stopPropagation();
onAddToQuery(fieldKey, fieldValue, OPERATORS['=']);
onAddToQuery(fieldKey, fieldValue, OPERATORS['='], undefined, dataType);
};
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
@ -35,9 +37,20 @@ function AddToQueryHOC({
export interface AddToQueryHOCProps {
fieldKey: string;
fieldValue: string;
onAddToQuery: (fieldKey: string, fieldValue: string, operator: string) => void;
onAddToQuery: (
fieldKey: string,
fieldValue: string,
operator: string,
isJSON?: boolean,
dataType?: DataTypes,
) => void;
fontSize: FontSize;
dataType?: DataTypes;
children: ReactNode;
}
AddToQueryHOC.defaultProps = {
dataType: DataTypes.EMPTY,
};
export default memo(AddToQueryHOC);

View File

@ -3,6 +3,7 @@ import { Button, Col, Popover } from 'antd';
import { OPERATORS } from 'constants/queryBuilder';
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
import { memo, useCallback, useMemo } from 'react';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
function ActionItem({
fieldKey,
@ -55,6 +56,8 @@ export interface ActionItemProps {
fieldKey: string,
fieldValue: string,
operator: string,
isJSON?: boolean,
dataType?: DataTypes,
) => void;
}

View File

@ -33,7 +33,12 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { ActionItemProps } from './ActionItem';
import FieldRenderer from './FieldRenderer';
import { TableViewActions } from './TableView/TableViewActions';
import { filterKeyForField, findKeyPath, flattenObject } from './utils';
import {
filterKeyForField,
findKeyPath,
flattenObject,
getFieldAttributes,
} from './utils';
interface TableViewProps {
logData: ILog;
@ -107,10 +112,17 @@ function TableView({
operator: string,
fieldKey: string,
fieldValue: string,
dataType: string | undefined,
): void => {
const validatedFieldValue = removeJSONStringifyQuotes(fieldValue);
if (onClickActionItem) {
onClickActionItem(fieldKey, validatedFieldValue, operator);
onClickActionItem(
fieldKey,
validatedFieldValue,
operator,
undefined,
dataType as DataTypes,
);
}
};
@ -118,8 +130,9 @@ function TableView({
operator: string,
fieldKey: string,
fieldValue: string,
dataType: string | undefined,
) => (): void => {
handleClick(operator, fieldKey, fieldValue);
handleClick(operator, fieldKey, fieldValue, dataType);
if (operator === OPERATORS['=']) {
setIsFilterInLoading(true);
}
@ -247,6 +260,7 @@ function TableView({
}
const fieldFilterKey = filterKeyForField(field);
const { dataType } = getFieldAttributes(field);
if (!RESTRICTED_SELECTED_FIELDS.includes(fieldFilterKey)) {
return (
<AddToQueryHOC
@ -254,6 +268,7 @@ function TableView({
fieldValue={flattenLogData[field]}
onAddToQuery={onAddToQuery}
fontSize={FontSize.SMALL}
dataType={dataType as DataTypes}
>
{renderedField}
</AddToQueryHOC>

View File

@ -23,6 +23,7 @@ import { DataType } from '../TableView';
import {
escapeHtml,
filterKeyForField,
getFieldAttributes,
jsonToDataNodes,
parseFieldValue,
recursiveParseJSON,
@ -45,6 +46,7 @@ interface ITableViewActionsProps {
operator: string,
fieldKey: string,
fieldValue: string,
dataType: string | undefined,
) => () => void;
}
@ -64,6 +66,7 @@ export function TableViewActions(
} = props;
const { pathname } = useLocation();
const { dataType } = getFieldAttributes(record.field);
// there is no option for where clause in old logs explorer and live logs page
const isOldLogsExplorerOrLiveLogsPage = useMemo(
@ -161,6 +164,7 @@ export function TableViewActions(
OPERATORS['='],
fieldFilterKey,
parseFieldValue(fieldData.value),
dataType,
)}
/>
</Tooltip>
@ -178,6 +182,7 @@ export function TableViewActions(
OPERATORS['!='],
fieldFilterKey,
parseFieldValue(fieldData.value),
dataType,
)}
/>
</Tooltip>

View File

@ -0,0 +1,296 @@
import { initialAutocompleteData } from 'constants/queryBuilder';
import {
BaseAutocompleteData,
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { chooseAutocompleteFromCustomValue } from '../newQueryBuilder/chooseAutocompleteFromCustomValue';
describe('chooseAutocompleteFromCustomValue', () => {
// Mock source list containing various data types and edge cases
const mockSourceList = [
{
key: 'string_key',
dataType: DataTypes.String,
isJSON: false,
},
{
key: 'number_key',
dataType: DataTypes.Float64,
isJSON: false,
},
{
key: 'bool_key',
dataType: DataTypes.bool,
isJSON: false,
},
{
key: 'float_key',
dataType: DataTypes.Float64,
isJSON: false,
},
{
key: 'unknown_key',
dataType: DataTypes.EMPTY,
isJSON: false,
},
{
key: 'duplicate_key',
dataType: DataTypes.String,
isJSON: false,
},
{
key: 'duplicate_key',
dataType: DataTypes.Float64,
isJSON: false,
},
] as BaseAutocompleteData[];
describe('when element with same value and same data type found in sourceList', () => {
// Test case: Perfect match - both key and dataType match an existing element
it('should return matching string element', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'string_key',
false,
'string' as DataTypes,
);
expect(result).toEqual(mockSourceList[0]);
});
// Test case: Perfect match for numeric data type
it('should return matching number element', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'number_key',
false,
'number',
);
expect(result).toEqual(mockSourceList[1]);
});
// Test case: Perfect match for boolean data type
it('should return matching bool element', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'bool_key',
false,
'bool' as DataTypes,
);
expect(result).toEqual(mockSourceList[2]);
});
// Test case: Perfect match for float data type (maps to Float64)
it('should return matching float element', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'float_key',
false,
'number',
);
expect(result).toEqual(mockSourceList[3]);
});
// Test case: Perfect match for unknown data type
it('should return matching unknown element', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'unknown_key',
false,
'unknown' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'unknown_key',
dataType: 'unknown',
isJSON: false,
});
});
// Test case: Perfect match with isJSON true in sourceList
it('should return matching element with isJSON true', () => {
const jsonSourceList = [
{ key: 'json_key', dataType: DataTypes.String, isJSON: true },
];
const result = chooseAutocompleteFromCustomValue(
jsonSourceList as BaseAutocompleteData[],
'json_key',
true,
'string' as DataTypes,
);
expect(result).toEqual(jsonSourceList[0]);
});
});
describe('when element with same value but different data type found in sourceList', () => {
// Test case: Key exists but dataType doesn't match - should create new object
it('should return new object for string value with number dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'string_key',
false,
'number',
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'string_key',
dataType: DataTypes.Float64,
isJSON: false,
});
});
// Test case: Key exists but dataType doesn't match - should create new object
it('should return new object for number value with string dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'number_key',
false,
'string' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'number_key',
dataType: DataTypes.String,
isJSON: false,
});
});
// Test case: Key exists but dataType doesn't match - should create new object
it('should return new object for bool value with string dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'bool_key',
false,
'string' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'bool_key',
dataType: DataTypes.String,
isJSON: false,
});
});
// Test case: Duplicate key with different dataType - should return the matching one
it('should return new object for duplicate key with different dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'duplicate_key',
false,
'number' as DataTypes,
);
expect(result).toEqual(mockSourceList[6]);
});
});
describe('when element not found in sourceList', () => {
// Test case: New key with string dataType - should create new object
it('should return new object for string dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'new_string_key',
false,
'string' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'new_string_key',
dataType: DataTypes.String,
isJSON: false,
});
});
// Test case: New key with number dataType - should create new object with Float64
it('should return new object for number dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'new_number_key',
false,
'number' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'new_number_key',
dataType: DataTypes.Float64,
isJSON: false,
});
});
// Test case: New key with boolean dataType - should create new object
it('should return new object for bool dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'new_bool_key',
false,
'bool' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'new_bool_key',
dataType: DataTypes.bool,
isJSON: false,
});
});
// Test case: New key with unknown dataType - should create new object with 'unknown' string
it('should return new object for unknown dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'new_unknown_key',
false,
'unknown' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'new_unknown_key',
dataType: 'unknown',
isJSON: false,
});
});
// Test case: New key with undefined dataType - should create new object with EMPTY
it('should return new object for undefined dataType', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'new_undefined_key',
false,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'new_undefined_key',
dataType: DataTypes.EMPTY,
isJSON: false,
});
});
// Test case: New key with isJSON true - should create new object with isJSON true
it('should return new object with isJSON true when not found', () => {
const result = chooseAutocompleteFromCustomValue(
mockSourceList,
'json_not_found',
true,
'string' as DataTypes,
);
expect(result).toEqual({
...initialAutocompleteData,
key: 'json_not_found',
dataType: DataTypes.String,
isJSON: true,
});
});
});
});

View File

@ -4,21 +4,40 @@ import {
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
const getDataTypeForCustomValue = (dataType?: string): DataTypes => {
if (dataType === 'number') {
return DataTypes.Float64;
}
if (dataType === 'string') {
return DataTypes.String;
}
if (dataType === 'bool') {
return DataTypes.bool;
}
return (dataType as DataTypes) || DataTypes.EMPTY;
};
export const chooseAutocompleteFromCustomValue = (
sourceList: BaseAutocompleteData[],
value: string,
isJSON?: boolean,
dataType?: DataTypes,
dataType?: DataTypes | 'number',
): BaseAutocompleteData => {
const dataTypeToUse = getDataTypeForCustomValue(dataType);
const firstBaseAutoCompleteValue = sourceList.find(
(sourceAutoComplete) => value === sourceAutoComplete.key,
(sourceAutoComplete) =>
value === sourceAutoComplete.key &&
(dataType === undefined || dataTypeToUse === sourceAutoComplete.dataType),
);
if (!firstBaseAutoCompleteValue) {
return {
...initialAutocompleteData,
key: value,
dataType: dataType || DataTypes.EMPTY,
dataType: dataTypeToUse,
isJSON,
};
}