From 76331001b79f64b03c547b538da0db9bc91582fd Mon Sep 17 00:00:00 2001 From: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Date: Fri, 12 May 2023 12:30:00 +0530 Subject: [PATCH] fix: issues on WHERE search filter (#2629) * fix: search filter validation on data source * fix: value search not working for in/nin * fix: unwanted key api while searching value & disabled tag * fix: unnecessary , at end of in/nin value * fix: added space after operator to get value * fix: custom value not being selected * fix: space after tag and value * fix: api call debounce duration * fix: suggested changes * fix: updated query params * fix: search filter data for logs and traces * fix: search filter value data type issue * fix: search filter value tag type * fix: chip & iscolumn issue * fix: null handled * fix: label in list of search filter component * fix: label in list of search filter component * fix: code level changes * fix: incorrect filter operators * fix: key selection dancing * fix: missing suggestion * fix: keys are not getting updated * fix: strange behaviour - removed duplicate options * fix: driver id exclusion not working * fix: loader when 0 options * fix: exists/not-exists tag value issue * fix: some weird behaviour about exists * fix: added duplicate option remove logic at hook level * fix: removed empty options from list * fix: closable chip handler on edit * fix: search filter validation on data source * fix: value search not working for in/nin * fix: unwanted key api while searching value & disabled tag * fix: unnecessary , at end of in/nin value * fix: added space after operator to get value * fix: custom value not being selected * fix: space after tag and value * fix: api call debounce duration * fix: suggested changes * fix: updated query params * fix: search filter data for logs and traces * fix: search filter value data type issue * fix: search filter value tag type * fix: chip & iscolumn issue * fix: null handled * fix: label in list of search filter component * fix: label in list of search filter component * fix: code level changes * fix: incorrect filter operators * fix: key selection dancing * fix: missing suggestion * fix: keys are not getting updated * fix: strange behaviour - removed duplicate options * fix: driver id exclusion not working * fix: loader when 0 options * fix: exists/not-exists tag value issue * fix: some weird behaviour about exists * fix: added duplicate option remove logic at hook level * fix: removed empty options from list * fix: closable chip handler on edit * fix: search filter validation on data source * fix: lint issues is fixed * fix: chip & iscolumn issue * fix: lint changes are updated * fix: undefined case handled * fix: undefined case handled * fix: removed settimeout * fix: delete chip getting value undefined * fix: payload correctness * fix: incorrect value selection * fix: key text typing doesn't change anything * fix: search value issue * fix: payload updated * fix: auto populate value issue * fix: payload updated & populate values * fix: split value for in/nin * fix: split value getting undefined * fix: new version of search filter using papaparse library * fix: removed unwanted space before operator * fix: added exact find method & removed includes logic * fix: issue when user create chip for exists not exists operator * fix: white space logic removed * fix: allow custom key in from list * fix: issue when user create chip for exists not exists operator * fix: removed unwanted includes * fix: removed unwanted utils function * fix: replaced join with papa unparse * fix: removed get count of space utils * fix: resolved build issue * fix: code level fixes * fix: space after key * fix: quote a value if comma present * fix: handle custom key object onchange * chore: coverted into string * Merge branch 'develop' into fix/issue-search-filter * chore: eslint rule disabling is removed * fix: serviceName contains sql * chore: less restrictive expression * fix: custom key selection issue * chore: papa parse version is made exact --------- Co-authored-by: Palash Gupta Co-authored-by: Srikanth Chekuri --- frontend/package.json | 2 + .../api/queryBuilder/getAggregateAttribute.ts | 7 +- .../src/api/queryBuilder/getAttributeKeys.ts | 9 +- .../queryBuilder/getAttributesKeysValues.ts | 63 ---------- .../api/queryBuilder/getAttributesValues.ts | 42 +++++++ frontend/src/constants/queryBuilder.ts | 19 ++- .../MetricsPageQueriesFactory.ts | 6 +- .../QueryBuilder/components/Query/Query.tsx | 4 +- .../filters/HavingFilter/HavingFilter.tsx | 1 - .../filters/QueryBuilderSearch/index.tsx | 73 ++++++++---- .../filters/QueryBuilderSearch/style.ts | 9 +- .../filters/QueryBuilderSearch/utils.ts | 105 +++++++++++++++- frontend/src/container/QueryBuilder/type.ts | 1 + .../TopNav/CustomDateTimeModal/index.tsx | 1 + .../src/hooks/queryBuilder/useAutoComplete.ts | 62 +++++----- .../queryBuilder/useFetchKeysAndValues.ts | 112 +++++++++++------- .../src/hooks/queryBuilder/useOperators.ts | 14 ++- frontend/src/hooks/queryBuilder/useOptions.ts | 93 +++++++++++---- .../hooks/queryBuilder/useQueryOperations.ts | 2 +- .../useSetCurrentKeyAndOperator.ts | 25 ++-- frontend/src/hooks/queryBuilder/useTag.ts | 33 +++--- .../hooks/queryBuilder/useTagValidation.ts | 16 ++- .../api/queryBuilder/getAttributeKeys.ts | 3 + .../api/queryBuilder/getAttributesValues.ts | 19 +++ .../api/queryBuilder/queryBuilderData.ts | 5 +- .../src/utils/checkStringEndsWithSpace.ts | 4 - frontend/src/utils/getCountOfSpace.ts | 1 - frontend/src/utils/getSearchParams.ts | 9 -- frontend/src/utils/separateSearchValue.ts | 12 -- 29 files changed, 491 insertions(+), 261 deletions(-) delete mode 100644 frontend/src/api/queryBuilder/getAttributesKeysValues.ts create mode 100644 frontend/src/api/queryBuilder/getAttributesValues.ts create mode 100644 frontend/src/types/api/queryBuilder/getAttributesValues.ts delete mode 100644 frontend/src/utils/checkStringEndsWithSpace.ts delete mode 100644 frontend/src/utils/getCountOfSpace.ts delete mode 100644 frontend/src/utils/getSearchParams.ts delete mode 100644 frontend/src/utils/separateSearchValue.ts diff --git a/frontend/package.json b/frontend/package.json index 24e883bb8b..e77720cdb8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -69,6 +69,7 @@ "less-loader": "^10.2.0", "lodash-es": "^4.17.21", "mini-css-extract-plugin": "2.4.5", + "papaparse": "5.4.1", "react": "18.2.0", "react-dom": "18.2.0", "react-force-graph": "^1.41.0", @@ -136,6 +137,7 @@ "@types/lodash-es": "^4.17.4", "@types/mini-css-extract-plugin": "^2.5.1", "@types/node": "^16.10.3", + "@types/papaparse": "5.3.7", "@types/react": "18.0.26", "@types/react-dom": "18.0.10", "@types/react-grid-layout": "^1.1.2", diff --git a/frontend/src/api/queryBuilder/getAggregateAttribute.ts b/frontend/src/api/queryBuilder/getAggregateAttribute.ts index 15e221d975..79ac5086f7 100644 --- a/frontend/src/api/queryBuilder/getAggregateAttribute.ts +++ b/frontend/src/api/queryBuilder/getAggregateAttribute.ts @@ -1,6 +1,7 @@ import { ApiV3Instance } from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError, AxiosResponse } from 'axios'; +import createQueryParams from 'lib/createQueryParams'; // ** Helpers import { ErrorResponse, SuccessResponse } from 'types/api'; // ** Types @@ -18,7 +19,11 @@ export const getAggregateAttribute = async ({ const response: AxiosResponse<{ data: IQueryAutocompleteResponse; }> = await ApiV3Instance.get( - `autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`, + `autocomplete/aggregate_attributes?${createQueryParams({ + aggregateOperator, + searchText, + dataSource, + })}`, ); return { diff --git a/frontend/src/api/queryBuilder/getAttributeKeys.ts b/frontend/src/api/queryBuilder/getAttributeKeys.ts index b42566a75e..2272be321d 100644 --- a/frontend/src/api/queryBuilder/getAttributeKeys.ts +++ b/frontend/src/api/queryBuilder/getAttributeKeys.ts @@ -1,6 +1,7 @@ import { ApiV3Instance } from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError, AxiosResponse } from 'axios'; +import createQueryParams from 'lib/createQueryParams'; import { ErrorResponse, SuccessResponse } from 'types/api'; // ** Types import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys'; @@ -11,6 +12,7 @@ export const getAggregateKeys = async ({ searchText, dataSource, aggregateAttribute, + tagType, }: IGetAttributeKeysPayload): Promise< SuccessResponse | ErrorResponse > => { @@ -18,7 +20,12 @@ export const getAggregateKeys = async ({ const response: AxiosResponse<{ data: IQueryAutocompleteResponse; }> = await ApiV3Instance.get( - `autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`, + `autocomplete/attribute_keys?${createQueryParams({ + aggregateOperator, + searchText, + dataSource, + aggregateAttribute, + })}&tagType=${tagType}`, ); return { diff --git a/frontend/src/api/queryBuilder/getAttributesKeysValues.ts b/frontend/src/api/queryBuilder/getAttributesKeysValues.ts deleted file mode 100644 index f5b938a345..0000000000 --- a/frontend/src/api/queryBuilder/getAttributesKeysValues.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ApiV3Instance } from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; -import { ErrorResponse, SuccessResponse } from 'types/api'; - -export type TagKeyValueProps = { - dataSource: string; - aggregateOperator?: string; - aggregateAttribute?: string; - searchText?: string; - attributeKey?: string; -}; - -export interface AttributeKeyOptions { - key: string; - type: string; - dataType: 'string' | 'boolean' | 'number'; - isColumn: boolean; -} - -export const getAttributesKeys = async ( - props: TagKeyValueProps, -): Promise | ErrorResponse> => { - try { - const response = await ApiV3Instance.get( - `/autocomplete/attribute_keys?aggregateOperator=${props.aggregateOperator}&dataSource=${props.dataSource}&aggregateAttribute=${props.aggregateAttribute}&searchText=${props.searchText}`, - ); - - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data.data.attributeKeys, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; - -export interface TagValuePayloadProps { - boolAttributeValues: null | string[]; - numberAttributeValues: null | string[]; - stringAttributeValues: null | string[]; -} - -export const getAttributesValues = async ( - props: TagKeyValueProps, -): Promise | ErrorResponse> => { - try { - const response = await ApiV3Instance.get( - `/autocomplete/attribute_values?aggregateOperator=${props.aggregateOperator}&dataSource=${props.dataSource}&aggregateAttribute=${props.aggregateAttribute}&searchText=${props.searchText}&attributeKey=${props.attributeKey}`, - ); - - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; diff --git a/frontend/src/api/queryBuilder/getAttributesValues.ts b/frontend/src/api/queryBuilder/getAttributesValues.ts new file mode 100644 index 0000000000..216da1e451 --- /dev/null +++ b/frontend/src/api/queryBuilder/getAttributesValues.ts @@ -0,0 +1,42 @@ +import { ApiV3Instance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import createQueryParams from 'lib/createQueryParams'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { + IAttributeValuesResponse, + IGetAttributeValuesPayload, +} from 'types/api/queryBuilder/getAttributesValues'; + +export const getAttributesValues = async ({ + aggregateOperator, + dataSource, + aggregateAttribute, + attributeKey, + filterAttributeKeyDataType, + tagType, + searchText, +}: IGetAttributeValuesPayload): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await ApiV3Instance.get( + `/autocomplete/attribute_values?${createQueryParams({ + aggregateOperator, + dataSource, + aggregateAttribute, + attributeKey, + searchText, + })}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 7c7fc8114f..18e423f7dc 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -34,6 +34,7 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str)); export enum QueryBuilderKeys { GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS', + GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY', } export const mapOfOperators: Record = { @@ -88,7 +89,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = { queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }), aggregateOperator: MetricAggregateOperator.NOOP, aggregateAttribute: initialAggregateAttribute, - tagFilters: { items: [], op: 'AND' }, + filters: { items: [], op: 'AND' }, expression: createNewBuilderItemName({ existNames: [], sourceNames: alphabet, @@ -166,7 +167,7 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = { OPERATORS.EXISTS, OPERATORS.NOT_EXISTS, ], - number: [ + int64: [ OPERATORS['='], OPERATORS['!='], OPERATORS.IN, @@ -178,7 +179,19 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = { OPERATORS['<='], OPERATORS['<'], ], - boolean: [ + float64: [ + OPERATORS['='], + OPERATORS['!='], + OPERATORS.IN, + OPERATORS.NIN, + OPERATORS.EXISTS, + OPERATORS.NOT_EXISTS, + OPERATORS['>='], + OPERATORS['>'], + OPERATORS['<='], + OPERATORS['<'], + ], + bool: [ OPERATORS['='], OPERATORS['!='], OPERATORS.EXISTS, diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts index 9acc614f47..57d4829ea3 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts @@ -25,7 +25,7 @@ export const getQueryBuilderQueries = ({ aggregateAttribute: metricName, legend, reduceTo: 'sum', - tagFilters: { + filters: { items: itemsA, op: 'AND', }, @@ -60,7 +60,7 @@ export const getQueryBuilderQuerieswithFormula = ({ legend, aggregateAttribute: metricNameA, reduceTo: 'sum', - tagFilters: { + filters: { items: additionalItemsA, op: 'AND', }, @@ -75,7 +75,7 @@ export const getQueryBuilderQuerieswithFormula = ({ queryName: 'B', expression: 'B', reduceTo: 'sum', - tagFilters: { + filters: { items: additionalItemsB, op: 'AND', }, diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index ee2f587026..2af4951210 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -81,8 +81,8 @@ export const Query = memo(function Query({ }, [handleChangeQueryData, query]); const handleChangeTagFilters = useCallback( - (value: IBuilderQuery['tagFilters']) => { - handleChangeQueryData('tagFilters', value); + (value: IBuilderQuery['filters']) => { + handleChangeQueryData('filters', value); }, [handleChangeQueryData], ); diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx index ebc1d7b826..f51189b8a6 100644 --- a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx @@ -34,7 +34,6 @@ export function HavingFilter({ ); const { isMulti } = useTagValidation( - searchText, currentFormValue.op, currentFormValue.value, ); diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index cc3b908752..8cc53d7b33 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -1,15 +1,23 @@ import { Select, Spin, Tag, Tooltip } from 'antd'; import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete'; +import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues'; import React, { useEffect, useMemo } from 'react'; import { IBuilderQuery, TagFilter, } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; import { v4 as uuid } from 'uuid'; import { selectStyle } from './config'; import { StyledCheckOutlined, TypographyText } from './style'; -import { isInNotInOperator } from './utils'; +import { + getOperatorValue, + getRemovePrefixFromKey, + getTagToken, + isExistsNotExistsOperator, + isInNInOperator, +} from './utils'; function QueryBuilderSearch({ query, @@ -26,18 +34,27 @@ function QueryBuilderSearch({ searchValue, isMulti, isFetching, + setSearchKey, + searchKey, } = useAutoComplete(query); + const { keys } = useFetchKeysAndValues(searchValue, query, searchKey); + const onTagRender = ({ value, closable, onClose, }: CustomTagProps): React.ReactElement => { - const isInNin = isInNotInOperator(value); + const { tagOperator } = getTagToken(value); + const isInNin = isInNInOperator(tagOperator); + const chipValue = isInNin + ? value?.trim()?.replace(/,\s*$/, '') + : value?.trim(); const onCloseHandler = (): void => { onClose(); handleSearch(''); + setSearchKey(''); }; const tagEditHandler = (value: string): void => { @@ -46,14 +63,16 @@ function QueryBuilderSearch({ }; return ( - - + + tagEditHandler(value)} > - {value} + {chipValue} @@ -66,43 +85,57 @@ function QueryBuilderSearch({ const onInputKeyDownHandler = (event: React.KeyboardEvent): void => { if (isMulti || event.key === 'Backspace') handleKeyDown(event); + if (isExistsNotExistsOperator(searchValue)) handleKeyDown(event); }; + const isMatricsDataSource = useMemo( + () => query.dataSource === DataSource.METRICS, + [query.dataSource], + ); + const queryTags = useMemo(() => { - if (!query.aggregateAttribute.key) return []; + if (!query.aggregateAttribute.key && isMatricsDataSource) return []; return tags; - }, [query.aggregateAttribute.key, tags]); + }, [isMatricsDataSource, query.aggregateAttribute.key, tags]); useEffect(() => { const initialTagFilters: TagFilter = { items: [], op: 'AND' }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore initialTagFilters.items = tags.map((tag) => { - const [tagKey, tagOperator, ...tagValue] = tag.split(' '); + const { tagKey, tagOperator, tagValue } = getTagToken(tag); + const filterAttribute = (keys || []).find( + (key) => key.key === getRemovePrefixFromKey(tagKey), + ); return { id: uuid().slice(0, 8), - // TODO: key should be fixed by Chintan Sudani - key: tagKey, - op: tagOperator, - value: tagValue.map((i) => i.replace(',', '')), + key: filterAttribute ?? { + key: tagKey, + dataType: null, + type: null, + isColumn: null, + }, + op: getOperatorValue(tagOperator), + value: + tagValue[tagValue.length - 1] === '' + ? tagValue?.slice(0, -1) + : tagValue ?? '', }; }); onChange(initialTagFilters); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tags]); + }, [keys, tags]); return (