From 342e94d093cf7cea2a1b1b3953ce150a21261cd0 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Tue, 6 Jun 2023 22:28:18 +0300 Subject: [PATCH] fix: query builder edit mode doesn't send correct payload (#2804) * fix: where clause getting values * fix: group by filter custom option * fix: id for group by and aggregate filters * fix: repeating values * refactor: group by uniq items * fix: removing source key * fix: keep where clause filter on operator change * chore: clean up for console log and additional variables --------- Co-authored-by: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Co-authored-by: Vishal Sharma Co-authored-by: Palash Gupta --- .../api/queryBuilder/getAggregateAttribute.ts | 9 ++- .../src/api/queryBuilder/getAttributeKeys.ts | 9 ++- frontend/src/constants/queryBuilder.ts | 17 +++-- .../AggregatorFilter/AggregatorFilter.tsx | 47 +++++++++----- .../filters/GroupByFilter/GroupByFilter.tsx | 63 ++++++++++--------- .../filters/QueryBuilderSearch/index.tsx | 25 ++++++-- .../queryBuilder/useFetchKeysAndValues.ts | 10 ++- .../hooks/queryBuilder/useQueryOperations.ts | 5 +- frontend/src/hooks/queryBuilder/useTag.ts | 28 +++++---- frontend/src/lib/createIdFromObjectFields.ts | 6 ++ .../newQueryBuilder/getFilterObjectValue.ts | 33 ---------- frontend/src/providers/QueryBuilder.tsx | 25 +++++++- 12 files changed, 167 insertions(+), 110 deletions(-) create mode 100644 frontend/src/lib/createIdFromObjectFields.ts delete mode 100644 frontend/src/lib/newQueryBuilder/getFilterObjectValue.ts diff --git a/frontend/src/api/queryBuilder/getAggregateAttribute.ts b/frontend/src/api/queryBuilder/getAggregateAttribute.ts index cbd6740f31..e493bb460a 100644 --- a/frontend/src/api/queryBuilder/getAggregateAttribute.ts +++ b/frontend/src/api/queryBuilder/getAggregateAttribute.ts @@ -1,6 +1,8 @@ import { ApiV3Instance } from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError, AxiosResponse } from 'axios'; +import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder'; +import { createIdFromObjectFields } from 'lib/createIdFromObjectFields'; import createQueryParams from 'lib/createQueryParams'; // ** Helpers import { ErrorResponse, SuccessResponse } from 'types/api'; @@ -10,7 +12,6 @@ import { BaseAutocompleteData, IQueryAutocompleteResponse, } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { v4 as uuid } from 'uuid'; export const getAggregateAttribute = async ({ aggregateOperator, @@ -31,8 +32,10 @@ export const getAggregateAttribute = async ({ ); const payload: BaseAutocompleteData[] = - response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) || - []; + response.data.data.attributeKeys?.map(({ id: _, ...item }) => ({ + ...item, + id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), + })) || []; return { statusCode: 200, diff --git a/frontend/src/api/queryBuilder/getAttributeKeys.ts b/frontend/src/api/queryBuilder/getAttributeKeys.ts index b47509de0a..99edc630c8 100644 --- a/frontend/src/api/queryBuilder/getAttributeKeys.ts +++ b/frontend/src/api/queryBuilder/getAttributeKeys.ts @@ -1,6 +1,8 @@ import { ApiV3Instance } from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError, AxiosResponse } from 'axios'; +import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder'; +import { createIdFromObjectFields } from 'lib/createIdFromObjectFields'; import createQueryParams from 'lib/createQueryParams'; import { ErrorResponse, SuccessResponse } from 'types/api'; // ** Types @@ -9,7 +11,6 @@ import { BaseAutocompleteData, IQueryAutocompleteResponse, } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { v4 as uuid } from 'uuid'; export const getAggregateKeys = async ({ aggregateOperator, @@ -33,8 +34,10 @@ export const getAggregateKeys = async ({ ); const payload: BaseAutocompleteData[] = - response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) || - []; + response.data.data.attributeKeys?.map(({ id: _, ...item }) => ({ + ...item, + id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), + })) || []; return { statusCode: 200, diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index b8e0e990b9..95faf6149d 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -1,7 +1,10 @@ // ** Helpers import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName'; -import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + BaseAutocompleteData, + LocalDataType, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; import { HavingForm, IBuilderFormula, @@ -35,7 +38,13 @@ import { export const MAX_FORMULAS = 20; export const MAX_QUERIES = 26; -export const selectValueDivider = '--'; +export const idDivider = '--'; +export const selectValueDivider = '__'; + +export const baseAutoCompleteIdKeysOrder: (keyof Omit< + BaseAutocompleteData, + 'id' +>)[] = ['key', 'dataType', 'type', 'isColumn']; export const formulasNames: string[] = Array.from( Array(MAX_FORMULAS), @@ -90,7 +99,7 @@ export const initialHavingValues: HavingForm = { value: [], }; -export const initialAggregateAttribute: IBuilderQuery['aggregateAttribute'] = { +export const initialAutocompleteData: BaseAutocompleteData = { id: uuid(), dataType: null, key: '', @@ -102,7 +111,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = { dataSource: DataSource.METRICS, queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }), aggregateOperator: MetricAggregateOperator.NOOP, - aggregateAttribute: initialAggregateAttribute, + aggregateAttribute: initialAutocompleteData, filters: { items: [], op: 'AND' }, expression: createNewBuilderItemName({ existNames: [], diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx index 63fbc6e4b5..a168d32d0c 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx @@ -3,19 +3,25 @@ import { AutoComplete, Spin } from 'antd'; // ** Api import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute'; import { - initialAggregateAttribute, + baseAutoCompleteIdKeysOrder, + idDivider, + initialAutocompleteData, QueryBuilderKeys, selectValueDivider, } from 'constants/queryBuilder'; import useDebounce from 'hooks/useDebounce'; -import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue'; +import { createIdFromObjectFields } from 'lib/createIdFromObjectFields'; import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import { memo, useCallback, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; +import { + AutocompleteType, + BaseAutocompleteData, + DataType, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataSource } from 'types/common/queryBuilder'; import { ExtendedSelectOption } from 'types/common/select'; import { transformToUpperCase } from 'utils/transformToUpperCase'; -import { v4 as uuid } from 'uuid'; import { selectStyle } from '../QueryBuilderSearch/config'; // ** Types @@ -27,7 +33,7 @@ export const AggregatorFilter = memo(function AggregatorFilter({ }: AgregatorFilterProps): JSX.Element { const [optionsData, setOptionsData] = useState([]); const debouncedValue = useDebounce(query.aggregateAttribute.key, 300); - const { data, isFetching } = useQuery( + const { isFetching } = useQuery( [ QueryBuilderKeys.GET_AGGREGATE_ATTRIBUTE, debouncedValue, @@ -44,18 +50,17 @@ export const AggregatorFilter = memo(function AggregatorFilter({ enabled: !!query.aggregateOperator && !!query.dataSource, onSuccess: (data) => { const options: ExtendedSelectOption[] = - data?.payload?.attributeKeys?.map((item) => ({ + data?.payload?.attributeKeys?.map(({ id: _, ...item }) => ({ label: transformStringWithPrefix({ str: item.key, prefix: item.type || '', condition: !item.isColumn, }), - value: `${transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - })}${selectValueDivider}${item.id || uuid()}`, - key: item.id || uuid(), + value: `${item.key}${selectValueDivider}${createIdFromObjectFields( + item, + baseAutoCompleteIdKeysOrder, + )}`, + key: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder), })) || []; setOptionsData(options); @@ -70,15 +75,23 @@ export const AggregatorFilter = memo(function AggregatorFilter({ ): void => { const currentOption = option as ExtendedSelectOption; - const { key } = getFilterObjectValue(value); + if (currentOption.key) { + const [key, dataType, type, isColumn] = currentOption.key.split(idDivider); + const attribute: BaseAutocompleteData = { + key, + dataType: dataType as DataType, + type: type as AutocompleteType, + isColumn: isColumn === 'true', + }; - const currentAttributeObj = data?.payload?.attributeKeys?.find( - (item) => currentOption.key === item.id, - ) || { ...initialAggregateAttribute, key }; + onChange(attribute); + } else { + const attribute = { ...initialAutocompleteData, key: value }; - onChange(currentAttributeObj); + onChange(attribute); + } }, - [data, onChange], + [onChange], ); const value = useMemo( diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx index 50fe67a29b..111ff2ea5c 100644 --- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx @@ -1,17 +1,25 @@ import { Select, Spin } from 'antd'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; // ** Constants -import { QueryBuilderKeys, selectValueDivider } from 'constants/queryBuilder'; +import { + idDivider, + initialAutocompleteData, + QueryBuilderKeys, + selectValueDivider, +} from 'constants/queryBuilder'; import useDebounce from 'hooks/useDebounce'; -import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue'; // ** Components // ** Helpers import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; -import { memo, useEffect, useState } from 'react'; +import { isEqual, uniqWith } from 'lodash-es'; +import { memo, useCallback, useEffect, useState } from 'react'; import { useQuery } from 'react-query'; -import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + AutocompleteType, + BaseAutocompleteData, + DataType, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; import { SelectOption } from 'types/common/select'; -import { v4 as uuid } from 'uuid'; import { selectStyle } from '../QueryBuilderSearch/config'; import { GroupByFilterProps } from './GroupByFilter.interfaces'; @@ -32,7 +40,7 @@ export const GroupByFilter = memo(function GroupByFilter({ const debouncedValue = useDebounce(searchText, 300); - const { data, isFetching } = useQuery( + const { isFetching } = useQuery( [QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedValue, isFocused], async () => getAggregateKeys({ @@ -65,7 +73,7 @@ export const GroupByFilter = memo(function GroupByFilter({ str: item.key, prefix: item.type || '', condition: !item.isColumn, - })}${selectValueDivider}${item.id || uuid()}`, + })}${selectValueDivider}${item.id}`, })) || []; setOptionsData(options); @@ -87,36 +95,32 @@ export const GroupByFilter = memo(function GroupByFilter({ }; const handleChange = (values: SelectOption[]): void => { - const responseKeys = data?.payload?.attributeKeys || []; - const groupByValues: BaseAutocompleteData[] = values.map((item) => { const [currentValue, id] = item.value.split(selectValueDivider); - const { key, type, isColumn } = getFilterObjectValue(currentValue); + if (id && id.includes(idDivider)) { + const [key, dataType, type, isColumn] = id.split(idDivider); - const existGroupQuery = query.groupBy.find((group) => group.id === id); - - if (existGroupQuery) { - return existGroupQuery; + return { + id, + key, + dataType: dataType as DataType, + type: type as AutocompleteType, + isColumn: isColumn === 'true', + }; } - const existGroupResponse = responseKeys.find((group) => group.id === id); - - if (existGroupResponse) { - return existGroupResponse; - } - - return { - id: uuid(), - isColumn, - key, - dataType: null, - type, - }; + return { ...initialAutocompleteData, key: currentValue }; }); - onChange(groupByValues); + const result = uniqWith(groupByValues, isEqual); + + onChange(result); }; + const clearSearch = useCallback(() => { + setSearchText(''); + }, []); + useEffect(() => { const currentValues: SelectOption[] = query.groupBy.map( (item) => ({ @@ -129,7 +133,7 @@ export const GroupByFilter = memo(function GroupByFilter({ str: item.key, prefix: item.type || '', condition: !item.isColumn, - })}${selectValueDivider}${item.id || uuid()}`, + })}${selectValueDivider}${item.id}`, }), ); @@ -147,6 +151,7 @@ export const GroupByFilter = memo(function GroupByFilter({ filterOption={false} onBlur={handleBlur} onFocus={handleFocus} + onDeselect={clearSearch} options={optionsData} value={localValues} labelInValue diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 5c4d340a45..093e71df7a 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -5,9 +5,11 @@ import { KeyboardEvent, ReactElement, ReactNode, + useCallback, useEffect, useMemo, } from 'react'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { IBuilderQuery, TagFilter, @@ -44,7 +46,11 @@ function QueryBuilderSearch({ searchKey, } = useAutoComplete(query); - const { sourceKeys } = useFetchKeysAndValues(searchValue, query, searchKey); + const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues( + searchValue, + query, + searchKey, + ); const onTagRender = ({ value, @@ -94,6 +100,14 @@ function QueryBuilderSearch({ if (isExistsNotExistsOperator(searchValue)) handleKeyDown(event); }; + const handleDeselect = useCallback( + (deselectedItem: string) => { + handleClearTag(deselectedItem); + handleRemoveSourceKey(deselectedItem); + }, + [handleClearTag, handleRemoveSourceKey], + ); + const isMetricsDataSource = useMemo( () => query.dataSource === DataSource.METRICS, [query.dataSource], @@ -106,9 +120,12 @@ function QueryBuilderSearch({ useEffect(() => { const initialTagFilters: TagFilter = { items: [], op: 'AND' }; + const initialSourceKeys = query.filters.items.map( + (item) => item.key as BaseAutocompleteData, + ); initialTagFilters.items = tags.map((tag) => { const { tagKey, tagOperator, tagValue } = getTagToken(tag); - const filterAttribute = sourceKeys.find( + const filterAttribute = [...initialSourceKeys, ...sourceKeys].find( (key) => key.key === getRemovePrefixFromKey(tagKey), ); return { @@ -128,7 +145,7 @@ function QueryBuilderSearch({ }); onChange(initialTagFilters); /* eslint-disable react-hooks/exhaustive-deps */ - }, [sourceKeys, tags]); + }, [sourceKeys]); return (