mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
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:
parent
bbb21f608f
commit
b11a4c0c21
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user