diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index 2af4951210..e972996798 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -14,11 +14,11 @@ import { GroupByFilter, HavingFilter, OperatorsSelect, + OrderByFilter, ReduceToFilter, } from 'container/QueryBuilder/filters'; import AggregateEveryFilter from 'container/QueryBuilder/filters/AggregateEveryFilter'; import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter'; -import { OrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryOperations'; @@ -233,6 +233,25 @@ export const Query = memo(function Query({ )} + {isMetricsDataSource && ( + + + + + + + + + + + )} {isMetricsDataSource && ( @@ -247,25 +266,26 @@ export const Query = memo(function Query({ - - - - - - - - - - - - + {!isMetricsDataSource && ( + + + + + + + + + + + )} + query.dataSource === DataSource.METRICS, + [query.dataSource], + ); + return ( onChange(Number(event.target.value))} diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx index f51189b8a6..e114238b28 100644 --- a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx @@ -14,6 +14,7 @@ import { import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Having, HavingForm } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource } from 'types/common/queryBuilder'; import { SelectOption } from 'types/common/select'; // ** Types @@ -214,6 +215,11 @@ export function HavingFilter({ setLocalValues(transformHavingToStringValue(having)); }, [having]); + const isMetricsDataSource = useMemo( + () => query.dataSource === DataSource.METRICS, + [query.dataSource], + ); + return ( : null} onChange={handleChange} /> diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts index e5b31df7bd..9907e5b88f 100644 --- a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts @@ -1,8 +1,9 @@ import { IOption } from 'hooks/useResourceAttribute/types'; import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; +import * as Papa from 'papaparse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { OrderByFilterValue } from './OrderByFilter.interfaces'; +export const orderByValueDelimiter = '|'; export function mapLabelValuePairs( arr: BaseAutocompleteData[], @@ -17,16 +18,27 @@ export function mapLabelValuePairs( return [ { label: `${label} asc`, - value: `${value} asc`, + value: `${value}${orderByValueDelimiter}asc`, }, { label: `${label} desc`, - value: `${value} desc`, + value: `${value}${orderByValueDelimiter}desc`, }, ]; }); } -export function getLabelFromValue(arr: OrderByFilterValue[]): string[] { - return arr.map((value) => value.label.split(' ')[0]); +export function getLabelFromValue(arr: string[]): string[] { + return arr.flat().map((item) => { + const match = Papa.parse(item, { delimiter: orderByValueDelimiter }); + if (match) { + const [key] = match.data as string[]; + return key[0]; + } + return item; + }); +} + +export function checkIfKeyPresent(str: string, valueToCheck: string): boolean { + return new RegExp(`\\(${valueToCheck}\\)`).test(str); } diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts index 5b6d8ae5cd..a93171168f 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts @@ -1 +1 @@ -export const selectStyle = { width: '100%', minWidth: '10rem' }; +export const selectStyle = { width: '100%', minWidth: '7.7rem' }; diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 8cc53d7b33..a5aca5feb4 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -88,15 +88,15 @@ function QueryBuilderSearch({ if (isExistsNotExistsOperator(searchValue)) handleKeyDown(event); }; - const isMatricsDataSource = useMemo( + const isMetricsDataSource = useMemo( () => query.dataSource === DataSource.METRICS, [query.dataSource], ); const queryTags = useMemo(() => { - if (!query.aggregateAttribute.key && isMatricsDataSource) return []; + if (!query.aggregateAttribute.key && isMetricsDataSource) return []; return tags; - }, [isMatricsDataSource, query.aggregateAttribute.key, tags]); + }, [isMetricsDataSource, query.aggregateAttribute.key, tags]); useEffect(() => { const initialTagFilters: TagFilter = { items: [], op: 'AND' }; @@ -135,7 +135,7 @@ function QueryBuilderSearch({ placeholder="Search Filter" value={queryTags} searchValue={searchValue} - disabled={isMatricsDataSource && !query.aggregateAttribute.key} + disabled={isMetricsDataSource && !query.aggregateAttribute.key} style={selectStyle} onSearch={handleSearch} onChange={onChangeHandler} diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts index 8054f158b6..9364d5b042 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts @@ -2,6 +2,8 @@ import { OPERATORS } from 'constants/queryBuilder'; // eslint-disable-next-line import/no-extraneous-dependencies import * as Papa from 'papaparse'; +import { orderByValueDelimiter } from '../OrderByFilter/utils'; + export const tagRegexp = /([a-zA-Z0-9_.:@$()\-/\\]+)\s*(!=|=|<=|<|>=|>|IN|NOT_IN|LIKE|NOT_LIKE|EXISTS|NOT_EXISTS|CONTAINS|NOT_CONTAINS)\s*([\s\S]*)/g; export function isInNInOperator(value: string): boolean { @@ -39,12 +41,13 @@ export function getTagToken(tag: string): ITagToken { export function isExistsNotExistsOperator(value: string): boolean { const { tagOperator } = getTagToken(value); return ( - tagOperator === OPERATORS.NOT_EXISTS || tagOperator === OPERATORS.EXISTS + tagOperator?.trim() === OPERATORS.NOT_EXISTS || + tagOperator?.trim() === OPERATORS.EXISTS ); } export function getRemovePrefixFromKey(tag: string): string { - return tag?.replace(/^(tag_|resource_)/, ''); + return tag?.replace(/^(tag_|resource_)/, '').trim(); } export function getOperatorValue(op: string): string { @@ -106,3 +109,12 @@ export function replaceStringWithMaxLength( export function checkCommaInValue(str: string): string { return str.includes(',') ? `"${str}"` : str; } + +export function getRemoveOrderFromValue(tag: string): string { + const match = Papa.parse(tag, { delimiter: orderByValueDelimiter }); + if (match) { + const [key] = match.data.flat() as string[]; + return key; + } + return tag; +} diff --git a/frontend/src/container/QueryBuilder/filters/index.ts b/frontend/src/container/QueryBuilder/filters/index.ts index f9dcba8eaf..d15a242fde 100644 --- a/frontend/src/container/QueryBuilder/filters/index.ts +++ b/frontend/src/container/QueryBuilder/filters/index.ts @@ -2,4 +2,5 @@ export { AggregatorFilter } from './AggregatorFilter'; export { GroupByFilter } from './GroupByFilter'; export { HavingFilter } from './HavingFilter'; export { OperatorsSelect } from './OperatorsSelect'; +export { OrderByFilter } from './OrderByFilter'; export { ReduceToFilter } from './ReduceToFilter'; diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts index 104ad9183d..b0245e3f00 100644 --- a/frontend/src/hooks/queryBuilder/useAutoComplete.ts +++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts @@ -73,11 +73,9 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { return replaceStringWithMaxLength(prev, data as string[], value); }); } - if (!isMulti && isValidTag && !isExistsNotExistsOperator(value)) { - handleAddTag(value); - } - if (!isMulti && isValidTag && isExistsNotExistsOperator(value)) { - handleAddTag(value); + if (!isMulti) { + if (isExistsNotExistsOperator(value)) handleAddTag(value); + if (isValidTag && !isExistsNotExistsOperator(value)) handleAddTag(value); } }, [handleAddTag, isMulti, isValidTag], diff --git a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts index a4ad15ad5d..245f80506f 100644 --- a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts +++ b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts @@ -6,6 +6,7 @@ import { getTagToken, isInNInOperator, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; +import debounce from 'lodash-es/debounce'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useQuery } from 'react-query'; import { useDebounce } from 'react-use'; @@ -34,6 +35,25 @@ export const useFetchKeysAndValues = ( const [keys, setKeys] = useState([]); const [results, setResults] = useState([]); + const searchParams = useMemo( + () => + debounce( + () => [ + searchKey, + query.dataSource, + query.aggregateOperator, + query.aggregateAttribute.key, + ], + 300, + ), + [ + query.aggregateAttribute.key, + query.aggregateOperator, + query.dataSource, + searchKey, + ], + ); + const isQueryEnabled = useMemo( () => query.dataSource === DataSource.METRICS @@ -49,13 +69,7 @@ export const useFetchKeysAndValues = ( ); const { data, isFetching, status } = useQuery( - [ - QueryBuilderKeys.GET_ATTRIBUTE_KEY, - searchKey, - query.dataSource, - query.aggregateOperator, - query.aggregateAttribute.key, - ], + [QueryBuilderKeys.GET_ATTRIBUTE_KEY, searchParams()], async () => getAggregateKeys({ searchText: searchKey, diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts index f5388f393f..07a326852f 100644 --- a/frontend/src/hooks/queryBuilder/useOptions.ts +++ b/frontend/src/hooks/queryBuilder/useOptions.ts @@ -52,41 +52,39 @@ export const useOptions = ( ); useEffect(() => { + let newOptions: Option[] = []; + if (!key) { - setOptions( - searchValue - ? [ - { label: `${searchValue} `, value: `${searchValue} ` }, - ...getOptionsFromKeys(keys), - ] - : getOptionsFromKeys(keys), - ); + newOptions = searchValue + ? [ + { label: `${searchValue} `, value: `${searchValue} ` }, + ...getOptionsFromKeys(keys), + ] + : getOptionsFromKeys(keys); } else if (key && !operator) { - setOptions( - operators?.map((operator) => ({ - value: `${key} ${operator} `, - label: `${key} ${operator} `, - })), - ); + newOptions = operators?.map((operator) => ({ + value: `${key} ${operator} `, + label: `${key} ${operator} `, + })); } else if (key && operator) { if (isMulti) { - setOptions( - results.map((item) => ({ - label: checkCommaInValue(String(item)), - value: String(item), - })), - ); + newOptions = results.map((item) => ({ + label: checkCommaInValue(String(item)), + value: String(item), + })); } else if (isExist) { - setOptions([]); + newOptions = []; } else if (isValidOperator) { const hasAllResults = results.every((value) => result.includes(value)); const values = getKeyOpValue(results); - const options = hasAllResults + newOptions = hasAllResults ? [{ label: searchValue, value: searchValue }] : [{ label: searchValue, value: searchValue }, ...values]; - setOptions(options); } } + if (newOptions.length > 0) { + setOptions(newOptions); + } }, [ getKeyOpValue, getOptionsFromKeys, diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index 93a330e231..10dea4b03f 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -42,7 +42,6 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => { ...query, aggregateOperator: value, having: [], - orderBy: [], limit: null, filters: { items: [], op: 'AND' }, ...(shouldResetAggregateAttribute diff --git a/frontend/src/types/api/queryBuilder/queryBuilderData.ts b/frontend/src/types/api/queryBuilder/queryBuilderData.ts index e506ebb1c1..0dcd686f50 100644 --- a/frontend/src/types/api/queryBuilder/queryBuilderData.ts +++ b/frontend/src/types/api/queryBuilder/queryBuilderData.ts @@ -33,6 +33,11 @@ export type HavingForm = Omit & { value: string[]; }; +export type OrderByPayload = { + columnName: string; + order: string; +}; + // Type for query builder export type IBuilderQuery = { queryName: string; @@ -46,7 +51,7 @@ export type IBuilderQuery = { having: Having[]; limit: number | null; stepInterval: number; - orderBy: BaseAutocompleteData[]; + orderBy: OrderByPayload[]; reduceTo: ReduceOperators; legend: string; };