diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index ba174d8137..51fe841a4c 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -44,7 +44,7 @@ export const initialQueryBuilderFormValues: IBuilderQueryForm = { queryName: createNewQueryName([]), aggregateOperator: Object.values(MetricAggregateOperator)[0], aggregateAttribute: initialAggregateAttribute, - tagFilters: [], + tagFilters: { items: [], op: 'AND' }, expression: '', disabled: false, having: [], diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx index bb7cb47765..60f44a6007 100644 --- a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx +++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx @@ -1,4 +1,3 @@ -import { Row } from 'antd'; import React, { Fragment, memo, ReactNode, useState } from 'react'; // ** Types @@ -45,7 +44,7 @@ export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({ {isOpenedFilters ? : } {!isOpenedFilters && Add conditions for {filtersTexts}} - {isOpenedFilters && {children}} + {isOpenedFilters && children} ); }); diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts b/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts index cd271b2187..cab8afbc93 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts +++ b/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts @@ -20,3 +20,7 @@ export const StyledDeleteEntity = styled(CloseCircleOutlined)` export const StyledRow = styled(Row)` padding-right: 3rem; `; + +export const StyledFilterRow = styled(Row)` + margin-bottom: 0.875rem; +`; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index bccae6d46b..0d46d71a6a 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -21,20 +21,24 @@ import { } 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/useQueryBuilder'; import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator'; // ** Hooks import React, { memo, useCallback, useMemo } from 'react'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQueryForm, + TagFilter, +} from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { transformToUpperCase } from 'utils/transformToUpperCase'; // ** Types import { QueryProps } from './Query.interfaces'; // ** Styles -import { StyledDeleteEntity, StyledRow } from './Query.styled'; +import { StyledDeleteEntity, StyledFilterRow, StyledRow } from './Query.styled'; export const Query = memo(function Query({ index, @@ -66,10 +70,13 @@ export const Query = memo(function Query({ ...query, aggregateOperator: value, having: [], + groupBy: [], + orderBy: [], limit: null, + tagFilters: { items: [], op: 'AND' }, }; - if (!aggregateDataType || query.dataSource === DataSource.METRICS) { + if (!aggregateDataType) { handleSetQueryData(index, newQuery); return; } @@ -194,6 +201,17 @@ export const Query = memo(function Query({ [query.dataSource], ); + const handleChangeOrderByKeys = useCallback( + (values: BaseAutocompleteData[]): void => { + const newQuery: IBuilderQueryForm = { + ...query, + orderBy: values, + }; + handleSetQueryData(index, newQuery); + }, + [handleSetQueryData, index, query], + ); + const handleChangeLimit = useCallback( (value: number | null): void => { const newQuery: IBuilderQueryForm = { @@ -216,6 +234,17 @@ export const Query = memo(function Query({ [index, query, handleSetQueryData], ); + const handleChangeTagFilters = useCallback( + (value: TagFilter): void => { + const newQuery: IBuilderQueryForm = { + ...query, + tagFilters: value, + }; + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + return ( @@ -241,7 +270,7 @@ export const Query = memo(function Query({ {isMatricsDataSource && } - + @@ -279,20 +308,26 @@ export const Query = memo(function Query({ {!isMatricsDataSource && ( - - + + + + + + + + - + - + )} - + - + + !query.aggregateAttribute.key || + query.aggregateOperator === MetricAggregateOperator.NOOP, + [query.aggregateAttribute.key, query.aggregateOperator], + ); + return ( : null} + onChange={handleChange} + /> + ); +} diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts new file mode 100644 index 0000000000..ece36ae2f6 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts @@ -0,0 +1 @@ +export { OrderByFilter } from './OrderByFilter'; diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts new file mode 100644 index 0000000000..e5b31df7bd --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts @@ -0,0 +1,32 @@ +import { IOption } from 'hooks/useResourceAttribute/types'; +import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; + +import { OrderByFilterValue } from './OrderByFilter.interfaces'; + +export function mapLabelValuePairs( + arr: BaseAutocompleteData[], +): Array[] { + return arr.map((item) => { + const label = transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + }); + const value = item.key; + return [ + { + label: `${label} asc`, + value: `${value} asc`, + }, + { + label: `${label} desc`, + value: `${value} desc`, + }, + ]; + }); +} + +export function getLabelFromValue(arr: OrderByFilterValue[]): string[] { + return arr.map((value) => value.label.split(' ')[0]); +} diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index f31e813fc5..61c2b31930 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -1,13 +1,20 @@ import { Select, Spin, Tag, Tooltip } from 'antd'; import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete'; -import React from 'react'; -import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; +import React, { useEffect, useMemo } from 'react'; +import { + IBuilderQueryForm, + TagFilter, +} from 'types/api/queryBuilder/queryBuilderData'; +import { v4 as uuid } from 'uuid'; import { selectStyle } from './config'; import { StyledCheckOutlined, TypographyText } from './style'; import { isInNotInOperator } from './utils'; -function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element { +function QueryBuilderSearch({ + query, + onChange, +}: QueryBuilderSearchProps): JSX.Element { const { handleClearTag, handleKeyDown, @@ -51,6 +58,26 @@ function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element { if (isMulti || event.key === 'Backspace') handleKeyDown(event); }; + const queryTags = useMemo(() => { + if (!query.aggregateAttribute.key) return []; + return tags; + }, [query.aggregateAttribute.key, tags]); + + useEffect(() => { + const initialTagFilters: TagFilter = { items: [], op: 'AND' }; + initialTagFilters.items = tags.map((tag) => { + const [tagKey, tagOperator, ...tagValue] = tag.split(' '); + return { + id: uuid().slice(0, 8), + key: tagKey, + op: tagOperator, + value: tagValue.map((i) => i.replace(',', '')), + }; + }); + onChange(initialTagFilters); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tags]); + return (