diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts new file mode 100644 index 0000000000..5b881b4b27 --- /dev/null +++ b/frontend/src/constants/queryBuilder.ts @@ -0,0 +1,63 @@ +// ** TODO: use it for creating formula names +// import { createNewFormulaName } from 'lib/newQueryBuilder/createNewFormulaName'; +// ** Helpers +import { createNewQueryName } from 'lib/newQueryBuilder/createNewQueryName'; +import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; +import { + BoolOperators, + DataSource, + LogsAggregatorOperator, + MetricAggregateOperator, + NumberOperators, + StringOperators, + TracesAggregatorOperator, +} from 'types/common/queryBuilder'; + +export enum QueryBuilderKeys { + GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', + GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS', +} + +export const mapOfOperators: Record = { + metrics: Object.values(MetricAggregateOperator), + logs: Object.values(LogsAggregatorOperator), + traces: Object.values(TracesAggregatorOperator), +}; + +export const mapOfFilters: Record = { + // eslint-disable-next-line sonarjs/no-duplicate-string + metrics: ['Having', 'Aggregation interval'], + logs: ['Limit', 'Having', 'Order by', 'Aggregation interval'], + traces: ['Limit', 'Having', 'Order by', 'Aggregation interval'], +}; + +export const initialAggregateAttribute: IBuilderQueryForm['aggregateAttribute'] = { + dataType: null, + key: '', + isColumn: null, + type: null, +}; + +export const initialQueryBuilderFormValues: IBuilderQueryForm = { + dataSource: DataSource.METRICS, + queryName: createNewQueryName([]), + aggregateOperator: Object.values(MetricAggregateOperator)[0], + aggregateAttribute: initialAggregateAttribute, + tagFilters: [], + expression: '', + disabled: false, + having: [], + stepInterval: 30, + limit: 10, + orderBy: [], + groupBy: [], + legend: '', + reduceTo: '', +}; + +export const operatorsByTypes: Record = { + string: Object.values(StringOperators), + number: Object.values(NumberOperators), + bool: Object.values(BoolOperators), +}; diff --git a/frontend/src/constants/useQueryKeys.ts b/frontend/src/constants/useQueryKeys.ts deleted file mode 100644 index c765c17748..0000000000 --- a/frontend/src/constants/useQueryKeys.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum QueryBuilderKeys { - GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', - GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS', -} diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index 806ee579e9..8914457154 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -169,7 +169,7 @@ function QuerySection({ /> // TODO: uncomment for testing new QueryBuilder - // + // ), }, { diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index 3c0ccef6e8..01bc5726e5 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -1,3 +1,4 @@ +import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import { DataSource } from 'types/common/queryBuilder'; export type QueryBuilderConfig = @@ -9,4 +10,5 @@ export type QueryBuilderConfig = export type QueryBuilderProps = { config?: QueryBuilderConfig; + panelType?: ITEMS; }; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx index e8384eb2d9..dd51a2061e 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx +++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx @@ -1,35 +1,87 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { Button, Col, Row } from 'antd'; // ** Hooks import { useQueryBuilder } from 'hooks/useQueryBuilder'; -import React from 'react'; +import { MAX_FORMULAS } from 'lib/newQueryBuilder/createNewFormulaName'; +// ** Constants +import { MAX_QUERIES } from 'lib/newQueryBuilder/createNewQueryName'; +import React, { memo, useEffect, useMemo } from 'react'; // ** Components import { Query } from './components'; // ** Types import { QueryBuilderProps } from './QueryBuilder.interfaces'; +// ** Styles -// TODO: I think it can be components switcher, because if we have different views based on the data source, we can render based on source -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function QueryBuilder({ config }: QueryBuilderProps): JSX.Element { - const { queryBuilderData } = useQueryBuilder(); +export const QueryBuilder = memo(function QueryBuilder({ + config, + panelType, +}: QueryBuilderProps): JSX.Element { + const { + queryBuilderData, + setupInitialDataSource, + addNewQuery, + } = useQueryBuilder(); - // Here we can use Form from antd library and fill context data or edit - // Connect form with adding or removing items from the list + useEffect(() => { + if (config && config.queryVariant === 'static') { + setupInitialDataSource(config.initialDataSource); + } - // Here will be map of query queryBuilderData.queryData and queryBuilderData.queryFormulas components - // Each component can be part of antd Form list where we can add or remove items - // Also need decide to make a copy of queryData for working with form or not and after it set the full new list with formulas or queries to the context - // With button to add him - return ( -
- {queryBuilderData.queryData.map((query, index) => ( - 1} - queryVariant={config?.queryVariant || 'dropdown'} - query={query} - /> - ))} -
+ return (): void => { + setupInitialDataSource(null); + }; + }, [config, setupInitialDataSource]); + + const isDisabledQueryButton = useMemo( + () => queryBuilderData.queryData.length >= MAX_QUERIES, + [queryBuilderData], ); -} + + const isDisabledFormulaButton = useMemo( + () => queryBuilderData.queryData.length >= MAX_FORMULAS, + [queryBuilderData], + ); + + return ( + + + + {queryBuilderData.queryData.map((query, index) => ( + + 1} + queryVariant={config?.queryVariant || 'dropdown'} + query={query} + panelType={panelType} + /> + + ))} + + + + + + + + + + + + + ); +}); diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.interfaces.ts b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.interfaces.ts new file mode 100644 index 0000000000..8f92ec9eab --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.interfaces.ts @@ -0,0 +1,5 @@ +import { PropsWithChildren } from 'react'; + +export type AdditionalFiltersProps = PropsWithChildren & { + listOfAdditionalFilter: string[]; +}; diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.styled.ts b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.styled.ts new file mode 100644 index 0000000000..afa457b417 --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.styled.ts @@ -0,0 +1,40 @@ +import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons'; +import { Typography } from 'antd'; +import styled, { css } from 'styled-components'; + +const IconCss = css` + margin-right: 0.6875rem; + transition: all 0.2s ease; +`; + +export const StyledIconOpen = styled(PlusSquareOutlined)` + ${IconCss} +`; + +export const StyledIconClose = styled(MinusSquareOutlined)` + ${IconCss} +`; + +export const StyledWrapper = styled.div` + display: flex; + flex-direction: column; + width: fit-content; +`; + +export const StyledInner = styled.div` + width: 100%; + display: flex; + align-items: center; + margin-bottom: 0.875rem; + min-height: 1.375rem; + cursor: pointer; + &:hover { + ${StyledIconOpen}, ${StyledIconClose} { + opacity: 0.7; + } + } +`; + +export const StyledLink = styled(Typography.Link)` + pointer-events: none; +`; diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx new file mode 100644 index 0000000000..8e3f0e1889 --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx @@ -0,0 +1,52 @@ +import { Row } from 'antd'; +import React, { Fragment, memo, ReactNode, useState } from 'react'; + +// ** Types +import { AdditionalFiltersProps } from './AdditionalFiltersToggler.interfaces'; +// ** Styles +import { + StyledIconClose, + StyledIconOpen, + StyledInner, + StyledLink, + StyledWrapper, +} from './AdditionalFiltersToggler.styled'; + +export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({ + children, + listOfAdditionalFilter, +}: AdditionalFiltersProps): JSX.Element { + const [isOpenedFilters, setIsOpenedFilters] = useState(false); + + const handleToggleOpenFilters = (): void => { + setIsOpenedFilters((prevState) => !prevState); + }; + + const filtersTexts: ReactNode = listOfAdditionalFilter.map((str, index) => { + const isNextLast = index + 1 === listOfAdditionalFilter.length - 1; + if (index === listOfAdditionalFilter.length - 1) { + return ( + + and {str.toUpperCase()} + + ); + } + + return ( + + {str.toUpperCase()} + {isNextLast ? ' ' : ', '} + + ); + }); + + return ( + + + {isOpenedFilters ? : } + {!isOpenedFilters && Add conditions for {filtersTexts}} + + {isOpenedFilters && {children}} + + ); +}); diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/index.ts b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/index.ts new file mode 100644 index 0000000000..d08059abdf --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/index.ts @@ -0,0 +1 @@ +export { AdditionalFiltersToggler } from './AdditionalFiltersToggler'; diff --git a/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx index 4efe754f1a..2f5387398c 100644 --- a/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx +++ b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx @@ -1,5 +1,5 @@ import { Select } from 'antd'; -import React from 'react'; +import React, { memo } from 'react'; import { DataSource } from 'types/common/queryBuilder'; import { SelectOption } from 'types/common/select'; // ** Helpers @@ -10,7 +10,9 @@ import { QueryLabelProps } from './DataSourceDropdown.interfaces'; const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES]; -export function DataSourceDropdown(props: QueryLabelProps): JSX.Element { +export const DataSourceDropdown = memo(function DataSourceDropdown( + props: QueryLabelProps, +): JSX.Element { const { onChange, value, style } = props; const dataSourceOptions: SelectOption< @@ -30,4 +32,4 @@ export function DataSourceDropdown(props: QueryLabelProps): JSX.Element { style={style} /> ); -} +}); diff --git a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts index d379f6cbbe..fd66c62faf 100644 --- a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts +++ b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts @@ -5,6 +5,7 @@ export const StyledLabel = styled.div` width: fit-content; min-height: 2rem; display: inline-flex; + white-space: nowrap; align-items: center; border-radius: 0.125rem; border: 0.0625rem solid #434343; diff --git a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx index aa9eb85bf1..48153f4823 100644 --- a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx +++ b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { memo } from 'react'; // ** Types import { FilterLabelProps } from './FilterLabel.interfaces'; // ** Styles import { StyledLabel } from './FilterLabel.styled'; -export function FilterLabel({ label }: FilterLabelProps): JSX.Element { +export const FilterLabel = memo(function FilterLabel({ + label, +}: FilterLabelProps): JSX.Element { return {label}; -} +}); diff --git a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts index 6344456d0b..5c18783959 100644 --- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts +++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts @@ -1,12 +1,13 @@ import { Button } from 'antd'; import styled from 'styled-components'; -export const StyledButton = styled(Button)<{ isAvailableToDisable: boolean }>` +export const StyledButton = styled(Button)<{ $isAvailableToDisable: boolean }>` min-width: 2rem; height: 2.25rem; - padding: 0.125rem; + padding: ${(props): string => + props.$isAvailableToDisable ? '0.43rem' : '0.43rem 0.68rem'}; border-radius: 0.375rem; margin-right: 0.1rem; pointer-events: ${(props): string => - props.isAvailableToDisable ? 'default' : 'none'}; + props.$isAvailableToDisable ? 'default' : 'none'}; `; diff --git a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx index 8573579be7..a246047305 100644 --- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx +++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx @@ -1,13 +1,13 @@ import { EyeFilled, EyeInvisibleFilled } from '@ant-design/icons'; import { ButtonProps } from 'antd'; -import React from 'react'; +import React, { memo } from 'react'; // ** Types import { ListMarkerProps } from './ListMarker.interfaces'; // ** Styles import { StyledButton } from './ListMarker.styled'; -export function ListMarker({ +export const ListMarker = memo(function ListMarker({ isDisabled, labelName, index, @@ -30,10 +30,10 @@ export function ListMarker({ icon={buttonProps.icon} onClick={buttonProps.onClick} className={className} - isAvailableToDisable={isAvailableToDisable} + $isAvailableToDisable={isAvailableToDisable} style={style} > {labelName} ); -} +}); diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts index baf1bbac98..5420be12ea 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts +++ b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts @@ -1,3 +1,4 @@ +import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; export type QueryProps = { @@ -5,4 +6,5 @@ export type QueryProps = { isAvailableToDisable: boolean; query: IBuilderQueryForm; queryVariant: 'static' | 'dropdown'; + panelType?: ITEMS; }; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts b/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts new file mode 100644 index 0000000000..cd271b2187 --- /dev/null +++ b/frontend/src/container/QueryBuilder/components/Query/Query.styled.ts @@ -0,0 +1,22 @@ +import { CloseCircleOutlined } from '@ant-design/icons'; +import { Row } from 'antd'; +import styled from 'styled-components'; + +export const StyledDeleteEntity = styled(CloseCircleOutlined)` + position: absolute; + top: 0.9375rem; + right: 0.9375rem; + z-index: 1; + cursor: pointer; + opacity: 0.45; + width: 1.3125rem; + height: 1.3125rem; + svg { + width: 100%; + height: 100%; + } +`; + +export const StyledRow = styled(Row)` + padding-right: 3rem; +`; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index a41eb51e5d..4af3906699 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -1,7 +1,14 @@ -/* eslint-disable react/jsx-props-no-spreading */ -import { Col, Row } from 'antd'; +import { Col, Input, Row } from 'antd'; +// ** Constants +import { + initialAggregateAttribute, + mapOfFilters, + mapOfOperators, +} from 'constants/queryBuilder'; +import { initialQueryBuilderFormValues } from 'constants/queryBuilder'; // ** Components import { + AdditionalFiltersToggler, DataSourceDropdown, FilterLabel, ListMarker, @@ -10,64 +17,178 @@ import { AggregatorFilter, GroupByFilter, OperatorsSelect, + ReduceToFilter, } from 'container/QueryBuilder/filters'; // Context import { useQueryBuilder } from 'hooks/useQueryBuilder'; +import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator'; // ** Hooks -import React from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; -// ** Constants -import { - LogsAggregatorOperator, - MetricAggregateOperator, - TracesAggregatorOperator, -} from 'types/common/queryBuilder'; import { transformToUpperCase } from 'utils/transformToUpperCase'; // ** Types import { QueryProps } from './Query.interfaces'; +// ** Styles +import { StyledDeleteEntity, StyledRow } from './Query.styled'; -const mapOfOperators: Record = { - metrics: Object.values(MetricAggregateOperator), - logs: Object.values(LogsAggregatorOperator), - traces: Object.values(TracesAggregatorOperator), -}; - -export function Query({ +export const Query = memo(function Query({ index, isAvailableToDisable, queryVariant, query, + panelType, }: QueryProps): JSX.Element { - const { handleSetQueryData } = useQueryBuilder(); + const { + handleSetQueryData, + removeEntityByIndex, + initialDataSource, + } = useQueryBuilder(); - const currentListOfOperators = mapOfOperators[query.dataSource]; + const currentListOfOperators = useMemo( + () => mapOfOperators[query.dataSource], + [query], + ); + const listOfAdditionalFilters = useMemo(() => mapOfFilters[query.dataSource], [ + query, + ]); - const handleChangeOperator = (value: string): void => { - handleSetQueryData(index, { aggregateOperator: value }); - }; + const handleChangeOperator = useCallback( + (value: string): void => { + const aggregateDataType: BaseAutocompleteData['dataType'] = + query.aggregateAttribute.dataType; - const handleChangeDataSource = (nextSource: DataSource): void => { - handleSetQueryData(index, { dataSource: nextSource }); - }; + const newQuery: IBuilderQueryForm = { + ...query, + aggregateOperator: value, + having: [], + }; - const handleToggleDisableQuery = (): void => { - handleSetQueryData(index, { disabled: !query.disabled }); - }; + if (!aggregateDataType || query.dataSource === DataSource.METRICS) { + handleSetQueryData(index, newQuery); + return; + } - const handleChangeAggregatorAttribute = ( - value: BaseAutocompleteData, - ): void => { - handleSetQueryData(index, { aggregateAttribute: value }); - }; + switch (aggregateDataType) { + case 'string': + case 'bool': { + const typeOfValue = findDataTypeOfOperator(value); - const handleChangeGroupByKeys = (values: BaseAutocompleteData[]): void => { - handleSetQueryData(index, { groupBy: values }); - }; + handleSetQueryData(index, { + ...newQuery, + ...(typeOfValue === 'number' + ? { aggregateAttribute: initialAggregateAttribute } + : {}), + }); + + break; + } + case 'float64': + case 'int64': { + handleSetQueryData(index, newQuery); + + break; + } + + default: { + handleSetQueryData(index, newQuery); + break; + } + } + }, + [index, query, handleSetQueryData], + ); + + const handleChangeAggregatorAttribute = useCallback( + (value: BaseAutocompleteData): void => { + const newQuery: IBuilderQueryForm = { + ...query, + aggregateAttribute: value, + }; + + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + + const handleChangeDataSource = useCallback( + (nextSource: DataSource): void => { + let newQuery: IBuilderQueryForm = { + ...query, + dataSource: nextSource, + }; + + if (nextSource !== query.dataSource) { + const initCopy = { + ...(initialQueryBuilderFormValues as Partial), + }; + delete initCopy.queryName; + + newQuery = { + ...newQuery, + ...initCopy, + dataSource: initialDataSource || nextSource, + aggregateOperator: mapOfOperators[nextSource][0], + }; + } + + handleSetQueryData(index, newQuery); + }, + [index, query, initialDataSource, handleSetQueryData], + ); + + const handleToggleDisableQuery = useCallback((): void => { + const newQuery: IBuilderQueryForm = { + ...query, + disabled: !query.disabled, + }; + + handleSetQueryData(index, newQuery); + }, [index, query, handleSetQueryData]); + + const handleChangeGroupByKeys = useCallback( + (values: BaseAutocompleteData[]): void => { + const newQuery: IBuilderQueryForm = { + ...query, + groupBy: values, + }; + + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + + const handleChangeQueryLegend = useCallback( + (e: React.ChangeEvent): void => { + const newQuery: IBuilderQueryForm = { + ...query, + legend: e.target.value, + }; + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + + const handleChangeReduceTo = useCallback( + (value: string): void => { + const newQuery: IBuilderQueryForm = { + ...query, + reduceTo: value, + }; + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + + const handleDeleteQuery = useCallback(() => { + removeEntityByIndex('queryData', index); + }, [removeEntityByIndex, index]); return ( - + + @@ -92,14 +213,14 @@ export function Query({ - + - + - - + + - - + + {panelType === 'VALUE' ? ( + + ) : ( + + )} - + + + {/* TODO: Render filter by Col component */} + test additional filter + + + + + + ); -} +}); diff --git a/frontend/src/container/QueryBuilder/components/index.ts b/frontend/src/container/QueryBuilder/components/index.ts index f21c33d5c9..c009562b22 100644 --- a/frontend/src/container/QueryBuilder/components/index.ts +++ b/frontend/src/container/QueryBuilder/components/index.ts @@ -1,3 +1,4 @@ +export { AdditionalFiltersToggler } from './AdditionalFiltersToggler'; export { DataSourceDropdown } from './DataSourceDropdown'; export { FilterLabel } from './FilterLabel'; export { Formula } from './Formula'; diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx index 6eebe2f955..174967b8f7 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx @@ -2,8 +2,9 @@ import { AutoComplete, Spin } from 'antd'; // ** Api import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute'; +import { initialAggregateAttribute } from 'constants/queryBuilder'; import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; -import React, { useMemo, useState } from 'react'; +import React, { memo, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; import { SelectOption } from 'types/common/select'; import { transformToUpperCase } from 'utils/transformToUpperCase'; @@ -11,7 +12,7 @@ import { transformToUpperCase } from 'utils/transformToUpperCase'; // ** Types import { AgregatorFilterProps } from './AggregatorFilter.intefaces'; -export function AggregatorFilter({ +export const AggregatorFilter = memo(function AggregatorFilter({ onChange, query, }: AgregatorFilterProps): JSX.Element { @@ -50,7 +51,7 @@ export function AggregatorFilter({ const handleChangeAttribute = (value: string): void => { const currentAttributeObj = data?.payload?.attributeKeys?.find( (item) => item.key === value, - ) || { key: value, type: null, dataType: null, isColumn: null }; + ) || { ...initialAggregateAttribute, key: value }; onChange(currentAttributeObj); }; @@ -79,4 +80,4 @@ export function AggregatorFilter({ onChange={handleChangeAttribute} /> ); -} +}); diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx index 3cda80d704..ff37393d8f 100644 --- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx @@ -2,11 +2,11 @@ import { Select, Spin } from 'antd'; // ** Api import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; // ** Constants -import { QueryBuilderKeys } from 'constants/useQueryKeys'; +import { QueryBuilderKeys } from 'constants/queryBuilder'; // ** Components // ** Helpers import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; -import React, { useState } from 'react'; +import React, { memo, useState } from 'react'; import { useQuery } from 'react-query'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { SelectOption } from 'types/common/select'; @@ -17,7 +17,7 @@ import { GroupByFilterValue, } from './GroupByFilter.interfaces'; -export function GroupByFilter({ +export const GroupByFilter = memo(function GroupByFilter({ query, onChange, }: GroupByFilterProps): JSX.Element { @@ -97,4 +97,4 @@ export function GroupByFilter({ onChange={handleChange} /> ); -} +}); diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx index e0bf89e6d2..9ff0a4f425 100644 --- a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx +++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx @@ -1,5 +1,5 @@ import { Select } from 'antd'; -import React from 'react'; +import React, { memo } from 'react'; // ** Types import { SelectOption } from 'types/common/select'; // ** Helpers @@ -7,7 +7,7 @@ import { transformToUpperCase } from 'utils/transformToUpperCase'; import { OperatorsSelectProps } from './OperatorsSelect.interfaces'; -export function OperatorsSelect({ +export const OperatorsSelect = memo(function OperatorsSelect({ operators, value, onChange, @@ -30,4 +30,4 @@ export function OperatorsSelect({ {...props} /> ); -} +}); diff --git a/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.interfaces.ts b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.interfaces.ts new file mode 100644 index 0000000000..8f9ee7b05c --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.interfaces.ts @@ -0,0 +1,7 @@ +import { SelectProps } from 'antd'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; + +export type ReduceToFilterProps = Omit & { + query: IBuilderQueryForm; + onChange: (value: string) => void; +}; diff --git a/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.tsx b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.tsx new file mode 100644 index 0000000000..1e99bd24c0 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.tsx @@ -0,0 +1,26 @@ +import { Select } from 'antd'; +import React, { memo } from 'react'; +// ** Types +import { EReduceOperator } from 'types/common/queryBuilder'; +import { SelectOption } from 'types/common/select'; + +import { ReduceToFilterProps } from './ReduceToFilter.interfaces'; + +export const ReduceToFilter = memo(function ReduceToFilter({ + query, + onChange, +}: ReduceToFilterProps): JSX.Element { + const options: SelectOption[] = Object.values( + EReduceOperator, + ).map((str) => ({ label: str, value: str })); + + return ( +