From b9b63a0ac4f4b47fb094c9b3c97d03a47c9f7a1a Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Tue, 28 Mar 2023 05:56:49 +0530 Subject: [PATCH 01/90] chore: update pr_verify_linked_issue workflow --- .github/workflows/pr_verify_linked_issue.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_verify_linked_issue.yml b/.github/workflows/pr_verify_linked_issue.yml index 3fd5abd2ec..722def7267 100644 --- a/.github/workflows/pr_verify_linked_issue.yml +++ b/.github/workflows/pr_verify_linked_issue.yml @@ -5,7 +5,7 @@ name: VerifyIssue on: pull_request: - types: [edited, synchronize, opened, reopened] + types: [edited, opened] check_run: jobs: @@ -14,6 +14,6 @@ jobs: name: Ensure Pull Request has a linked issue. steps: - name: Verify Linked Issue - uses: hattan/verify-linked-issue-action@v1.1.0 + uses: srikanthccv/verify-linked-issue-action@v0.69 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3e37a8b3648b17b2e17b04e0afed5efa09e7320c Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Tue, 28 Mar 2023 06:06:10 +0530 Subject: [PATCH 02/90] chore: update version --- .github/workflows/pr_verify_linked_issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_verify_linked_issue.yml b/.github/workflows/pr_verify_linked_issue.yml index 722def7267..a2442cc3a4 100644 --- a/.github/workflows/pr_verify_linked_issue.yml +++ b/.github/workflows/pr_verify_linked_issue.yml @@ -14,6 +14,6 @@ jobs: name: Ensure Pull Request has a linked issue. steps: - name: Verify Linked Issue - uses: srikanthccv/verify-linked-issue-action@v0.69 + uses: srikanthccv/verify-linked-issue-action@v0.70 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 776aa3471ffaa1b6cefd3130e22762a13a26b46d Mon Sep 17 00:00:00 2001 From: Chintan Sudani Date: Fri, 31 Mar 2023 09:36:34 +0530 Subject: [PATCH 03/90] fix: remove frontend code owner --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c1aa885b6a..fd42658745 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,6 +2,6 @@ # Owners are automatically requested for review for PRs that changes code # that they own. * @ankitnayan -/frontend/ @palashgdev @pranshuchittora +/frontend/ @palashgdev /deploy/ @prashant-shahi **/query-service/ @srikanthccv From c74896b213f396aa0ebbe37519a66468a59f8182 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Tue, 4 Apr 2023 16:42:52 +0530 Subject: [PATCH 04/90] fix: cursor pointer is removed the table view (#2547) --- frontend/src/components/Logs/TableView/styles.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/Logs/TableView/styles.ts b/frontend/src/components/Logs/TableView/styles.ts index cd361b9c5e..951404afec 100644 --- a/frontend/src/components/Logs/TableView/styles.ts +++ b/frontend/src/components/Logs/TableView/styles.ts @@ -16,6 +16,4 @@ export const TableBodyContent = styled.div` font-size: 0.875rem; line-height: 2rem; - - cursor: pointer; `; From d4bfe3a096f6cd114d9642ac27c1d78cc6b42b2a Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 5 Apr 2023 12:36:38 +0530 Subject: [PATCH 05/90] chore: set Cache-Control for auto complete requests (#2504) --- pkg/query-service/app/http_handler.go | 9 ++++++--- pkg/query-service/app/http_utils.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 pkg/query-service/app/http_utils.go diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index a617201233..4778afa723 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -255,9 +255,12 @@ func (aH *APIHandler) RegisterMetricsRoutes(router *mux.Router, am *AuthMiddlewa func (aH *APIHandler) RegisterQueryRangeV3Routes(router *mux.Router, am *AuthMiddleware) { subRouter := router.PathPrefix("/api/v3").Subrouter() - subRouter.HandleFunc("/autocomplete/aggregate_attributes", am.ViewAccess(aH.autocompleteAggregateAttributes)).Methods(http.MethodGet) - subRouter.HandleFunc("/autocomplete/attribute_keys", am.ViewAccess(aH.autoCompleteAttributeKeys)).Methods(http.MethodGet) - subRouter.HandleFunc("/autocomplete/attribute_values", am.ViewAccess(aH.autoCompleteAttributeValues)).Methods(http.MethodGet) + subRouter.HandleFunc("/autocomplete/aggregate_attributes", am.ViewAccess( + withCacheControl(AutoCompleteCacheControlAge, aH.autocompleteAggregateAttributes))).Methods(http.MethodGet) + subRouter.HandleFunc("/autocomplete/attribute_keys", am.ViewAccess( + withCacheControl(AutoCompleteCacheControlAge, aH.autoCompleteAttributeKeys))).Methods(http.MethodGet) + subRouter.HandleFunc("/autocomplete/attribute_values", am.ViewAccess( + withCacheControl(AutoCompleteCacheControlAge, aH.autoCompleteAttributeValues))).Methods(http.MethodGet) subRouter.HandleFunc("/query_range", am.ViewAccess(aH.QueryRangeV3)).Methods(http.MethodPost) } diff --git a/pkg/query-service/app/http_utils.go b/pkg/query-service/app/http_utils.go new file mode 100644 index 0000000000..2066044836 --- /dev/null +++ b/pkg/query-service/app/http_utils.go @@ -0,0 +1,22 @@ +package app + +import ( + "fmt" + "net/http" + "time" +) + +var ( + // AutoCompleteCacheControlAge is the max-age for the cache-control header + // for the autocomplete endpoint. + // The default export interval for the SDK is 60 seconds, and ~10s at collector, so + // one minute should be a safe value. + AutoCompleteCacheControlAge = 60 * time.Second +) + +func withCacheControl(maxAge time.Duration, h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", int(maxAge.Seconds()))) + h(w, r) + } +} From 5f73a82d9f943ee58338512234e76fb3661b28e6 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Wed, 5 Apr 2023 12:32:15 +0300 Subject: [PATCH 06/90] feat(filter): add group by filter (#2538) --- .../src/api/queryBuilder/getAttributeKeys.ts | 3 +- frontend/src/constants/useQueryKeys.ts | 1 + .../FilterLabel/FilterLabel.styled.ts | 1 - .../ListMarker/ListMarker.styled.ts | 5 +- .../components/ListMarker/ListMarker.tsx | 3 +- .../QueryBuilder/components/Query/Query.tsx | 87 +++++++++------ .../AggregatorFilter.intefaces.ts | 4 +- .../AggregatorFilter/AggregatorFilter.tsx | 6 +- .../GroupByFilter/GroupByFilter.interfaces.ts | 15 +++ .../filters/GroupByFilter/GroupByFilter.tsx | 100 ++++++++++++++++++ .../filters/GroupByFilter/index.ts | 1 + .../OperatorsSelect/OperatorsSelect.tsx | 1 + .../container/QueryBuilder/filters/index.ts | 1 + frontend/src/providers/QueryBuilder.tsx | 1 + .../api/queryBuilder/getAttributeKeys.ts | 3 + .../queryBuilder/queryAutocompleteResponse.ts | 4 +- .../api/queryBuilder/queryBuilderData.ts | 6 +- 17 files changed, 197 insertions(+), 45 deletions(-) create mode 100644 frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.interfaces.ts create mode 100644 frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx create mode 100644 frontend/src/container/QueryBuilder/filters/GroupByFilter/index.ts diff --git a/frontend/src/api/queryBuilder/getAttributeKeys.ts b/frontend/src/api/queryBuilder/getAttributeKeys.ts index b42566a75e..439c20f132 100644 --- a/frontend/src/api/queryBuilder/getAttributeKeys.ts +++ b/frontend/src/api/queryBuilder/getAttributeKeys.ts @@ -11,6 +11,7 @@ export const getAggregateKeys = async ({ searchText, dataSource, aggregateAttribute, + tagType, }: IGetAttributeKeysPayload): Promise< SuccessResponse | ErrorResponse > => { @@ -18,7 +19,7 @@ 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?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&tagType=${tagType}&searchText=${searchText}`, ); return { diff --git a/frontend/src/constants/useQueryKeys.ts b/frontend/src/constants/useQueryKeys.ts index 705d5ef350..c765c17748 100644 --- a/frontend/src/constants/useQueryKeys.ts +++ b/frontend/src/constants/useQueryKeys.ts @@ -1,3 +1,4 @@ export enum QueryBuilderKeys { GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', + GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS', } diff --git a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts index 8eb97a32f3..d379f6cbbe 100644 --- a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts +++ b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts @@ -2,7 +2,6 @@ import styled from 'styled-components'; export const StyledLabel = styled.div` padding: 0 0.6875rem; - min-width: 6.5rem; width: fit-content; min-height: 2rem; display: inline-flex; diff --git a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts index f876af973b..6344456d0b 100644 --- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts +++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts @@ -1,9 +1,12 @@ import { Button } from 'antd'; import styled from 'styled-components'; -export const StyledButton = styled(Button)` +export const StyledButton = styled(Button)<{ isAvailableToDisable: boolean }>` min-width: 2rem; height: 2.25rem; padding: 0.125rem; border-radius: 0.375rem; + margin-right: 0.1rem; + pointer-events: ${(props): string => + 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 3af9d3aa37..8573579be7 100644 --- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx +++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx @@ -30,7 +30,8 @@ export function ListMarker({ icon={buttonProps.icon} onClick={buttonProps.onClick} className={className} - style={{ marginRight: '0.1rem', ...style }} + isAvailableToDisable={isAvailableToDisable} + style={style} > {labelName} diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index 81427bc260..a41eb51e5d 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -8,13 +8,14 @@ import { } from 'container/QueryBuilder/components'; import { AggregatorFilter, + GroupByFilter, OperatorsSelect, } from 'container/QueryBuilder/filters'; // Context import { useQueryBuilder } from 'hooks/useQueryBuilder'; // ** Hooks import React from 'react'; -import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataSource } from 'types/common/queryBuilder'; // ** Constants import { @@ -55,40 +56,66 @@ export function Query({ handleSetQueryData(index, { disabled: !query.disabled }); }; - const handleChangeAggregatorAttribute = (value: AutocompleteData): void => { + const handleChangeAggregatorAttribute = ( + value: BaseAutocompleteData, + ): void => { handleSetQueryData(index, { aggregateAttribute: value }); }; + const handleChangeGroupByKeys = (values: BaseAutocompleteData[]): void => { + handleSetQueryData(index, { groupBy: values }); + }; + return ( - - - - {queryVariant === 'dropdown' ? ( - - ) : ( - - )} - + - - + + + + {queryVariant === 'dropdown' ? ( + + ) : ( + + )} + {/* TODO: here will be search */} + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts index 81ba34a2c2..e8e4f072fb 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts @@ -1,7 +1,7 @@ -import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; export type AgregatorFilterProps = { - onChange: (value: AutocompleteData) => void; + onChange: (value: BaseAutocompleteData) => void; query: IBuilderQueryForm; }; diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx index 13a25216ce..6eebe2f955 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx @@ -68,10 +68,8 @@ export function AggregatorFilter({ return ( void; +}; + +export type GroupByFilterValue = { + disabled: boolean | undefined; + key: string; + label: string; + title: string | undefined; + value: string; +}; diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx new file mode 100644 index 0000000000..3cda80d704 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx @@ -0,0 +1,100 @@ +import { Select, Spin } from 'antd'; +// ** Api +import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; +// ** Constants +import { QueryBuilderKeys } from 'constants/useQueryKeys'; +// ** Components +// ** Helpers +import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; +import React, { useState } from 'react'; +import { useQuery } from 'react-query'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { SelectOption } from 'types/common/select'; + +// ** Types +import { + GroupByFilterProps, + GroupByFilterValue, +} from './GroupByFilter.interfaces'; + +export function GroupByFilter({ + query, + onChange, +}: GroupByFilterProps): JSX.Element { + const [searchText, setSearchText] = useState(''); + + const { data, isFetching } = useQuery( + [QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText], + async () => + getAggregateKeys({ + aggregateAttribute: query.aggregateAttribute.key, + tagType: query.aggregateAttribute.type, + dataSource: query.dataSource, + aggregateOperator: query.aggregateOperator, + searchText, + }), + { enabled: !!query.aggregateAttribute.key, keepPreviousData: true }, + ); + + const handleSearchKeys = (searchText: string): void => { + setSearchText(searchText); + }; + + const optionsData: SelectOption[] = + data?.payload?.attributeKeys?.map((item) => ({ + label: transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + }), + value: item.key, + })) || []; + + const handleChange = (values: GroupByFilterValue[]): void => { + const groupByValues: BaseAutocompleteData[] = values.map((item) => { + const iterationArray = data?.payload?.attributeKeys || query.groupBy; + const existGroup = iterationArray.find((group) => group.key === item.value); + if (existGroup) { + return existGroup; + } + + return { + isColumn: null, + key: item.value, + dataType: null, + type: null, + }; + }); + + onChange(groupByValues); + }; + + const values: GroupByFilterValue[] = query.groupBy.map((item) => ({ + label: transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + }), + key: item.key, + value: item.key, + disabled: undefined, + title: undefined, + })); + + return ( + + + ); -} +}); 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 ( + : null} + > + {options?.map((option) => ( + + {option.value} + {option.selected && } + + ))} + + ); +} + +interface QueryBuilderSearchProps { + query: IBuilderQueryForm; +} + +export interface CustomTagProps { + label: React.ReactNode; + value: string; + disabled: boolean; + onClose: (event?: React.MouseEvent) => void; + closable: boolean; +} + +export default QueryBuilderSearch; diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts new file mode 100644 index 0000000000..064392b773 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts @@ -0,0 +1,11 @@ +import { CheckOutlined } from '@ant-design/icons'; +import { Typography } from 'antd'; +import styled from 'styled-components'; + +export const TypographyText = styled(Typography.Text)<{ isInNin: boolean }>` + width: ${({ isInNin }): string => (isInNin ? '10rem' : 'auto')}; +`; + +export const StyledCheckOutlined = styled(CheckOutlined)` + float: right; +`; diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts new file mode 100644 index 0000000000..7ff23055eb --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts @@ -0,0 +1,9 @@ +import { OPERATORS } from 'constants/queryBuilder'; + +export function isInNotInOperator(value: string): boolean { + return value?.includes(OPERATORS.IN || OPERATORS.NIN); +} + +export function isExistsNotExistsOperator(value: string): boolean { + return value?.includes(OPERATORS.EXISTS || OPERATORS.NOT_EXISTS); +} diff --git a/frontend/src/container/QueryBuilder/type.ts b/frontend/src/container/QueryBuilder/type.ts new file mode 100644 index 0000000000..85cfe42e84 --- /dev/null +++ b/frontend/src/container/QueryBuilder/type.ts @@ -0,0 +1,16 @@ +import { IQueryBuilderState } from 'constants/queryBuilder'; + +export interface InitialStateI { + search: string; +} + +export interface ContextValueI { + values: InitialStateI; + onChangeHandler: (type: IQueryBuilderState) => (value: string) => void; + onSubmitHandler: VoidFunction; +} + +export type Option = { + value: string; + selected?: boolean; +}; diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts new file mode 100644 index 0000000000..49ce84e109 --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts @@ -0,0 +1,131 @@ +import { isExistsNotExistsOperator } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; +import { Option } from 'container/QueryBuilder/type'; +import { useCallback, useState } from 'react'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; +import { checkStringEndsWithSpace } from 'utils/checkStringEndsWithSpace'; + +import { useFetchKeysAndValues } from './useFetchKeysAndValues'; +import { useOptions } from './useOptions'; +import { useSetCurrentKeyAndOperator } from './useSetCurrentKeyAndOperator'; +import { useTag } from './useTag'; +import { useTagValidation } from './useTagValidation'; + +interface IAutoComplete { + handleSearch: (value: string) => void; + handleClearTag: (value: string) => void; + handleSelect: (value: string) => void; + handleKeyDown: (event: React.KeyboardEvent) => void; + options: Option[]; + tags: string[]; + searchValue: string; + isMulti: boolean; + isFetching: boolean; +} + +export const useAutoComplete = (query: IBuilderQueryForm): IAutoComplete => { + const [searchValue, setSearchValue] = useState(''); + + const handleSearch = (value: string): void => setSearchValue(value); + + const { keys, results, isFetching } = useFetchKeysAndValues( + searchValue, + query, + ); + + const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys); + + const { + isValidTag, + isExist, + isValidOperator, + isMulti, + isFreeText, + } = useTagValidation(searchValue, operator, result); + + const { handleAddTag, handleClearTag, tags } = useTag( + key, + isValidTag, + isFreeText, + handleSearch, + ); + + const handleSelect = useCallback( + (value: string): void => { + if (isMulti) { + setSearchValue((prev: string) => { + if (prev.includes(value)) { + return prev.replace(` ${value}`, ''); + } + return checkStringEndsWithSpace(prev) + ? `${prev} ${value}` + : `${prev}, ${value}`; + }); + } + if (!isMulti && isValidTag && !isExistsNotExistsOperator(value)) { + handleAddTag(value); + } + if (!isMulti && isExistsNotExistsOperator(value)) { + handleAddTag(value); + } + }, + [handleAddTag, isMulti, isValidTag], + ); + + const handleKeyDown = useCallback( + (event: React.KeyboardEvent): void => { + if ( + event.key === ' ' && + (searchValue.endsWith(' ') || searchValue.length === 0) + ) { + event.preventDefault(); + } + + if (event.key === 'Enter' && searchValue && isValidTag) { + if (isMulti || isFreeText) { + event.stopPropagation(); + } + event.preventDefault(); + handleAddTag(searchValue); + } + + if (event.key === 'Backspace' && !searchValue) { + event.stopPropagation(); + const last = tags[tags.length - 1]; + handleClearTag(last); + } + }, + [ + handleAddTag, + handleClearTag, + isFreeText, + isMulti, + isValidTag, + searchValue, + tags, + ], + ); + + const options = useOptions( + key, + keys, + operator, + searchValue, + isMulti, + isValidOperator, + isExist, + results, + result, + ); + + return { + handleSearch, + handleClearTag, + handleSelect, + handleKeyDown, + options, + tags, + searchValue, + isMulti, + isFetching, + }; +}; diff --git a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts new file mode 100644 index 0000000000..3fd04181dc --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts @@ -0,0 +1,106 @@ +import { + AttributeKeyOptions, + getAttributesKeys, + getAttributesValues, +} from 'api/queryBuilder/getAttributesKeysValues'; +import { useEffect, useRef, useState } from 'react'; +import { useQuery } from 'react-query'; +import { useDebounce } from 'react-use'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; +import { separateSearchValue } from 'utils/separateSearchValue'; + +type UseFetchKeysAndValuesReturnValues = { + keys: AttributeKeyOptions[]; + results: string[]; + isFetching: boolean; +}; + +/** + * Custom hook to fetch attribute keys and values from an API + * @param searchValue - the search query value + * @param query - an object containing data for the query + * @returns an object containing the fetched attribute keys, results, and the status of the fetch + */ + +export const useFetchKeysAndValues = ( + searchValue: string, + query: IBuilderQueryForm, +): UseFetchKeysAndValuesReturnValues => { + const [keys, setKeys] = useState([]); + const [results, setResults] = useState([]); + const { data, isFetching, status } = useQuery( + [ + 'GET_ATTRIBUTE_KEY', + searchValue, + query.dataSource, + query.aggregateOperator, + query.aggregateAttribute.key, + ], + async () => + getAttributesKeys({ + searchText: searchValue, + dataSource: query.dataSource, + aggregateOperator: query.aggregateOperator, + aggregateAttribute: query.aggregateAttribute.key, + }), + { enabled: !!query.aggregateOperator && !!query.dataSource }, + ); + + /** + * Fetches the options to be displayed based on the selected value + * @param value - the selected value + * @param query - an object containing data for the query + */ + const handleFetchOption = async ( + value: string, + query: IBuilderQueryForm, + ): Promise => { + if (value) { + // separate the search value into the attribute key and the operator + const [tKey, operator] = separateSearchValue(value); + setResults([]); + if (tKey && operator) { + const { payload } = await getAttributesValues({ + searchText: searchValue, + dataSource: query.dataSource, + aggregateOperator: query.aggregateOperator, + aggregateAttribute: query.aggregateAttribute.key, + attributeKey: tKey, + }); + if (payload) { + const values = Object.values(payload).find((el) => !!el); + if (values) { + setResults(values); + } else { + setResults([]); + } + } + } + } + }; + + // creates a ref to the fetch function so that it doesn't change on every render + const clearFetcher = useRef(handleFetchOption).current; + + // debounces the fetch function to avoid excessive API calls + useDebounce(() => clearFetcher(searchValue, query), 500, [ + clearFetcher, + searchValue, + query, + ]); + + // update the fetched keys when the fetch status changes + useEffect(() => { + if (status === 'success' && data?.payload) { + setKeys(data?.payload); + } else { + setKeys([]); + } + }, [data?.payload, status]); + + return { + keys, + results, + isFetching, + }; +}; diff --git a/frontend/src/hooks/queryBuilder/useIsValidTag.ts b/frontend/src/hooks/queryBuilder/useIsValidTag.ts new file mode 100644 index 0000000000..216971b0ac --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useIsValidTag.ts @@ -0,0 +1,22 @@ +import { useMemo } from 'react'; + +import { OperatorType } from './useOperatorType'; + +const validationMapper: Record< + OperatorType, + (resultLength: number) => boolean +> = { + SINGLE_VALUE: (resultLength: number) => resultLength === 1, + MULTIPLY_VALUE: (resultLength: number) => resultLength >= 1, + NON_VALUE: (resultLength: number) => resultLength === 0, + NOT_VALID: () => false, +}; + +export const useIsValidTag = ( + operatorType: OperatorType, + resultLength: number, +): boolean => + useMemo(() => validationMapper[operatorType]?.(resultLength), [ + operatorType, + resultLength, + ]); diff --git a/frontend/src/hooks/queryBuilder/useOperatorType.ts b/frontend/src/hooks/queryBuilder/useOperatorType.ts new file mode 100644 index 0000000000..a387708301 --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useOperatorType.ts @@ -0,0 +1,27 @@ +import { OPERATORS } from 'constants/queryBuilder'; + +export type OperatorType = + | 'SINGLE_VALUE' + | 'MULTIPLY_VALUE' + | 'NON_VALUE' + | 'NOT_VALID'; + +const operatorTypeMapper: Record = { + [OPERATORS.IN]: 'MULTIPLY_VALUE', + [OPERATORS.NIN]: 'MULTIPLY_VALUE', + [OPERATORS.EXISTS]: 'NON_VALUE', + [OPERATORS.NOT_EXISTS]: 'NON_VALUE', + [OPERATORS.LTE]: 'SINGLE_VALUE', + [OPERATORS.LT]: 'SINGLE_VALUE', + [OPERATORS.GTE]: 'SINGLE_VALUE', + [OPERATORS.GT]: 'SINGLE_VALUE', + [OPERATORS.LIKE]: 'SINGLE_VALUE', + [OPERATORS.NLIKE]: 'SINGLE_VALUE', + [OPERATORS.CONTAINS]: 'SINGLE_VALUE', + [OPERATORS.NOT_CONTAINS]: 'SINGLE_VALUE', + [OPERATORS.EQUALS]: 'SINGLE_VALUE', + [OPERATORS.NOT_EQUALS]: 'SINGLE_VALUE', +}; + +export const useOperatorType = (operator: string): OperatorType => + operatorTypeMapper[operator] || 'NOT_VALID'; diff --git a/frontend/src/hooks/queryBuilder/useOperators.ts b/frontend/src/hooks/queryBuilder/useOperators.ts new file mode 100644 index 0000000000..b56fed1dee --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useOperators.ts @@ -0,0 +1,20 @@ +import { AttributeKeyOptions } from 'api/queryBuilder/getAttributesKeysValues'; +import { QUERY_BUILDER_OPERATORS_BY_TYPES } from 'constants/queryBuilder'; +import { useMemo } from 'react'; + +type IOperators = + | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.universal + | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.string + | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.boolean + | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.number; + +export const useOperators = ( + key: string, + keys: AttributeKeyOptions[], +): IOperators => + useMemo(() => { + const currentKey = keys?.find((el) => el.key === key); + return currentKey + ? QUERY_BUILDER_OPERATORS_BY_TYPES[currentKey.dataType] + : QUERY_BUILDER_OPERATORS_BY_TYPES.universal; + }, [keys, key]); diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts new file mode 100644 index 0000000000..3b378f0684 --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useOptions.ts @@ -0,0 +1,78 @@ +import { AttributeKeyOptions } from 'api/queryBuilder/getAttributesKeysValues'; +import { Option } from 'container/QueryBuilder/type'; +import { useCallback, useEffect, useMemo, useState } from 'react'; + +import { useOperators } from './useOperators'; + +export const useOptions = ( + key: string, + keys: AttributeKeyOptions[], + operator: string, + searchValue: string, + isMulti: boolean, + isValidOperator: boolean, + isExist: boolean, + results: string[], + result: string[], +): Option[] => { + const [options, setOptions] = useState([]); + const operators = useOperators(key, keys); + + const updateOptions = useCallback(() => { + if (!key) { + setOptions( + searchValue + ? [{ value: searchValue }, ...keys.map((k) => ({ value: k.key }))] + : keys?.map((k) => ({ value: k.key })), + ); + } else if (key && !operator) { + setOptions( + operators.map((o) => ({ + value: `${key} ${o}`, + label: `${key} ${o.replace('_', ' ')}`, + })), + ); + } else if (key && operator) { + if (isMulti) { + setOptions(results.map((r) => ({ value: `${r}` }))); + } else if (isExist) { + setOptions([]); + } else if (isValidOperator) { + const hasAllResults = result.every((val) => results.includes(val)); + const values = results.map((r) => ({ + value: `${key} ${operator} ${r}`, + })); + const options = hasAllResults + ? values + : [{ value: searchValue }, ...values]; + setOptions(options); + } + } + }, [ + isExist, + isMulti, + isValidOperator, + key, + keys, + operator, + operators, + result, + results, + searchValue, + ]); + + useEffect(() => { + updateOptions(); + }, [updateOptions]); + + return useMemo( + () => + options?.map((option) => { + if (isMulti) { + return { ...option, selected: searchValue.includes(option.value) }; + } + return option; + }), + [isMulti, options, searchValue], + ); +}; diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts new file mode 100644 index 0000000000..cd1e7cec2f --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts @@ -0,0 +1,32 @@ +import { AttributeKeyOptions } from 'api/queryBuilder/getAttributesKeysValues'; +import { useMemo } from 'react'; +import { getCountOfSpace } from 'utils/getCountOfSpace'; +import { separateSearchValue } from 'utils/separateSearchValue'; + +type ICurrentKeyAndOperator = [string, string, string[]]; + +export const useSetCurrentKeyAndOperator = ( + value: string, + keys: AttributeKeyOptions[], +): ICurrentKeyAndOperator => { + const [key, operator, result] = useMemo(() => { + let key = ''; + let operator = ''; + let result: string[] = []; + + if (value) { + const [tKey, tOperator, tResult] = separateSearchValue(value); + const isSuggestKey = keys?.some((el) => el.key === tKey); + + if (getCountOfSpace(value) >= 1 || isSuggestKey) { + key = tKey || ''; + operator = tOperator || ''; + result = tResult.filter((el) => el); + } + } + + return [key, operator, result]; + }, [value, keys]); + + return [key, operator, result]; +}; diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts new file mode 100644 index 0000000000..38aef6fde8 --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -0,0 +1,53 @@ +import { isExistsNotExistsOperator } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; +import { useCallback, useState } from 'react'; + +type IUseTag = { + handleAddTag: (value: string) => void; + handleClearTag: (value: string) => void; + tags: string[]; +}; + +/** + * A custom React hook for handling tags. + * @param {string} key - A string value to identify tags. + * @param {boolean} isValidTag - A boolean value to indicate whether the tag is valid. + * @param {boolean} isFreeText - A boolean value to indicate whether free text is allowed. + * @param {function} handleSearch - A callback function to handle search. + * @returns {IUseTag} The return object containing handlers and tags. + */ +export const useTag = ( + key: string, + isValidTag: boolean, + isFreeText: boolean, + handleSearch: (value: string) => void, +): IUseTag => { + const [tags, setTags] = useState([]); + + /** + * Adds a new tag to the tag list. + * @param {string} value - The tag value to be added. + */ + const handleAddTag = useCallback( + (value: string): void => { + if ( + (value && key && isValidTag) || + isFreeText || + isExistsNotExistsOperator(value) + ) { + setTags((prevTags) => [...prevTags, value]); + handleSearch(''); + } + }, + [key, isValidTag, isFreeText, handleSearch], + ); + + /** + * Removes a tag from the tag list. + * @param {string} value - The tag value to be removed. + */ + const handleClearTag = useCallback((value: string): void => { + setTags((prevTags) => prevTags.filter((v) => v !== value)); + }, []); + + return { handleAddTag, handleClearTag, tags }; +}; diff --git a/frontend/src/hooks/queryBuilder/useTagValidation.ts b/frontend/src/hooks/queryBuilder/useTagValidation.ts new file mode 100644 index 0000000000..fd3932b93a --- /dev/null +++ b/frontend/src/hooks/queryBuilder/useTagValidation.ts @@ -0,0 +1,35 @@ +import { QUERY_BUILDER_SEARCH_VALUES } from 'constants/queryBuilder'; +import { useMemo } from 'react'; +import { checkStringEndsWithSpace } from 'utils/checkStringEndsWithSpace'; + +import { useIsValidTag } from './useIsValidTag'; +import { useOperatorType } from './useOperatorType'; + +type ITagValidation = { + isValidTag: boolean; + isExist: boolean; + isValidOperator: boolean; + isMulti: boolean; + isFreeText: boolean; +}; + +export const useTagValidation = ( + value: string, + operator: string, + result: string[], +): ITagValidation => { + const operatorType = useOperatorType(operator); + const isValidTag = useIsValidTag(operatorType, result.length); + + const { isExist, isValidOperator, isMulti, isFreeText } = useMemo(() => { + const isExist = operatorType === QUERY_BUILDER_SEARCH_VALUES.NON; + const isValidOperator = + operatorType !== QUERY_BUILDER_SEARCH_VALUES.NOT_VALID; + const isMulti = operatorType === QUERY_BUILDER_SEARCH_VALUES.MULTIPLY; + const isFreeText = operator === '' && !checkStringEndsWithSpace(value); + + return { isExist, isValidOperator, isMulti, isFreeText }; + }, [operator, operatorType, value]); + + return { isValidTag, isExist, isValidOperator, isMulti, isFreeText }; +}; diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 905aa85886..90e72bdff4 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -1,7 +1,9 @@ // ** Helpers // ** Constants -import { initialQueryBuilderFormValues } from 'constants/queryBuilder'; -import { mapOfOperators } from 'constants/queryBuilder'; +import { + initialQueryBuilderFormValues, + mapOfOperators, +} from 'constants/queryBuilder'; import { createNewQueryName, MAX_QUERIES, diff --git a/frontend/src/utils/checkStringEndsWithSpace.ts b/frontend/src/utils/checkStringEndsWithSpace.ts new file mode 100644 index 0000000000..99769b151b --- /dev/null +++ b/frontend/src/utils/checkStringEndsWithSpace.ts @@ -0,0 +1,4 @@ +export const checkStringEndsWithSpace = (str: string): boolean => { + const endSpace = / $/; + return endSpace.test(str); +}; diff --git a/frontend/src/utils/getCountOfSpace.ts b/frontend/src/utils/getCountOfSpace.ts new file mode 100644 index 0000000000..168520afd2 --- /dev/null +++ b/frontend/src/utils/getCountOfSpace.ts @@ -0,0 +1 @@ +export const getCountOfSpace = (s: string): number => s.split(' ').length - 1; diff --git a/frontend/src/utils/getSearchParams.ts b/frontend/src/utils/getSearchParams.ts new file mode 100644 index 0000000000..7de4457f75 --- /dev/null +++ b/frontend/src/utils/getSearchParams.ts @@ -0,0 +1,9 @@ +export const getSearchParams = (newParams: { + [key: string]: string; +}): URLSearchParams => { + const params = new URLSearchParams(); + Object.entries(newParams).forEach(([key, value]) => { + params.set(key, value); + }); + return params; +}; diff --git a/frontend/src/utils/separateSearchValue.ts b/frontend/src/utils/separateSearchValue.ts new file mode 100644 index 0000000000..4499c478a8 --- /dev/null +++ b/frontend/src/utils/separateSearchValue.ts @@ -0,0 +1,12 @@ +import { OPERATORS } from 'constants/queryBuilder'; + +export const separateSearchValue = ( + value: string, +): [string, string, string[]] => { + const separatedString = value.split(' '); + const [key, operator, ...result] = separatedString; + if (operator === OPERATORS.IN || operator === OPERATORS.NIN) { + return [key, operator, result]; + } + return [key, operator, Array(result.join(' '))]; +}; From 0bc44c6fd986ecde0513a153dd37a5baf4a7b339 Mon Sep 17 00:00:00 2001 From: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Date: Sat, 15 Apr 2023 18:37:51 +0530 Subject: [PATCH 17/90] feat: Limt filter for QB (#2561) --- .../AdditionalFiltersToggler.tsx | 5 +-- .../QueryBuilder/components/Query/Query.tsx | 25 ++++++++++++- .../filters/LimitFilter/LimitFilter.tsx | 37 +++++++++++++++++++ .../api/queryBuilder/queryBuilderData.ts | 2 +- 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx index 8e3f0e1889..bb7cb47765 100644 --- a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx +++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx @@ -9,7 +9,6 @@ import { StyledIconOpen, StyledInner, StyledLink, - StyledWrapper, } from './AdditionalFiltersToggler.styled'; export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({ @@ -41,12 +40,12 @@ export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({ }); return ( - +
{isOpenedFilters ? : } {!isOpenedFilters && Add conditions for {filtersTexts}} {isOpenedFilters && {children}} - +
); }); diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index a5c27c6a6d..d6b1837081 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -19,6 +19,7 @@ import { OperatorsSelect, ReduceToFilter, } from 'container/QueryBuilder/filters'; +import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import { useQueryBuilder } from 'hooks/useQueryBuilder'; import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator'; @@ -64,6 +65,7 @@ export const Query = memo(function Query({ ...query, aggregateOperator: value, having: [], + limit: null, }; if (!aggregateDataType || query.dataSource === DataSource.METRICS) { @@ -191,6 +193,17 @@ export const Query = memo(function Query({ [query.dataSource], ); + const handleChangeLimit = useCallback( + (value: number | null): void => { + const newQuery: IBuilderQueryForm = { + ...query, + limit: value, + }; + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + return ( @@ -253,8 +266,16 @@ export const Query = memo(function Query({ - {/* TODO: Render filter by Col component */} - test additional filter + {!isMatricsDataSource && ( + + + + + + + + + )} diff --git a/frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx b/frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx new file mode 100644 index 0000000000..e9f136691f --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx @@ -0,0 +1,37 @@ +import { InputNumber } from 'antd'; +import React, { useState } from 'react'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; + +import { selectStyle } from '../QueryBuilderSearch/config'; + +function LimitFilter({ onChange, query }: LimitFilterProps): JSX.Element { + const [isData, setIsData] = useState(null); + const onChangeHandler = (value: number | null): void => { + setIsData(value); + }; + + const handleEnter = (e: { key: string }): void => { + if (e.key === 'Enter') { + onChange(isData); + } + }; + + return ( + + ); +} + +interface LimitFilterProps { + onChange: (values: number | null) => void; + query: IBuilderQueryForm; +} + +export default LimitFilter; diff --git a/frontend/src/types/api/queryBuilder/queryBuilderData.ts b/frontend/src/types/api/queryBuilder/queryBuilderData.ts index c70a1f4aeb..693d2910e5 100644 --- a/frontend/src/types/api/queryBuilder/queryBuilderData.ts +++ b/frontend/src/types/api/queryBuilder/queryBuilderData.ts @@ -40,7 +40,7 @@ export type IBuilderQuery = { expression: string; disabled: boolean; having: Having[]; - limit: number; + limit: number | null; stepInterval: number; orderBy: string[]; reduceTo: string; From 9aff047da4746e7109de58f875115b0f70ec1b8b Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Mon, 17 Apr 2023 19:56:14 +0530 Subject: [PATCH 18/90] =?UTF-8?q?chore:=20=F0=9F=94=A7=20=20remove=20resou?= =?UTF-8?q?rce=20requests/limits=20from=20sample=20apps=20(#2288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi Co-authored-by: Vishal Sharma Co-authored-by: palashgdev --- sample-apps/hotrod/hotrod-template.yaml | 21 --------------------- sample-apps/hotrod/hotrod.yaml | 21 --------------------- 2 files changed, 42 deletions(-) diff --git a/sample-apps/hotrod/hotrod-template.yaml b/sample-apps/hotrod/hotrod-template.yaml index 6fdd6dd9ae..f2d432ca4d 100644 --- a/sample-apps/hotrod/hotrod-template.yaml +++ b/sample-apps/hotrod/hotrod-template.yaml @@ -56,13 +56,6 @@ spec: name: hotrod ports: - containerPort: 8080 - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 200m - memory: 200Mi restartPolicy: Always --- apiVersion: v1 @@ -126,13 +119,6 @@ spec: name: comm-plus-1 - containerPort: 8089 name: web-ui - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 200m - memory: 200Mi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst @@ -203,13 +189,6 @@ spec: volumeMounts: - mountPath: /locust name: locust-scripts - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 200m - memory: 200Mi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst diff --git a/sample-apps/hotrod/hotrod.yaml b/sample-apps/hotrod/hotrod.yaml index dfad0132c7..63d7fc88de 100644 --- a/sample-apps/hotrod/hotrod.yaml +++ b/sample-apps/hotrod/hotrod.yaml @@ -56,13 +56,6 @@ spec: name: hotrod ports: - containerPort: 8080 - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 200m - memory: 200Mi restartPolicy: Always --- apiVersion: v1 @@ -126,13 +119,6 @@ spec: name: comm-plus-1 - containerPort: 8089 name: web-ui - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 200m - memory: 200Mi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst @@ -203,13 +189,6 @@ spec: volumeMounts: - mountPath: /locust name: locust-scripts - resources: - requests: - cpu: 100m - memory: 100Mi - limits: - cpu: 200m - memory: 200Mi terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst From 041d347d50e5e56753dea0f4a7e8c1eca8debcc5 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Mon, 17 Apr 2023 19:57:42 +0530 Subject: [PATCH 19/90] =?UTF-8?q?chore:=20=F0=9F=94=A7=20update=20install?= =?UTF-8?q?=20and=20troubleshooting=20guide=20url=20(#2451)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- README.de-de.md | 4 ++-- README.md | 4 ++-- README.pt-br.md | 4 ++-- README.zh-cn.md | 4 ++-- deploy/install.sh | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.de-de.md b/README.de-de.md index 55dd7f4c22..6587756b9b 100644 --- a/README.de-de.md +++ b/README.de-de.md @@ -85,9 +85,9 @@ Hier findest du die vollständige Liste von unterstützten Programmiersprachen - ### Bereitstellung mit Docker -Bitte folge den [hier](https://signoz.io/docs/deployment/docker/) aufgelisteten Schritten um deine Anwendung mit Docker bereitzustellen. +Bitte folge den [hier](https://signoz.io/docs/install/docker/) aufgelisteten Schritten um deine Anwendung mit Docker bereitzustellen. -Die [Anleitungen zur Fehlerbehebung](https://signoz.io/docs/deployment/troubleshooting) könnten hilfreich sein, falls du auf irgendwelche Schwierigkeiten stößt. +Die [Anleitungen zur Fehlerbehebung](https://signoz.io/docs/install/troubleshooting/) könnten hilfreich sein, falls du auf irgendwelche Schwierigkeiten stößt.

 

diff --git a/README.md b/README.md index 70779f3de5..4920ca9a26 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,9 @@ You can find the complete list of languages here - https://opentelemetry.io/docs ### Deploy using Docker -Please follow the steps listed [here](https://signoz.io/docs/deployment/docker/) to install using docker +Please follow the steps listed [here](https://signoz.io/docs/install/docker/) to install using docker -The [troubleshooting instructions](https://signoz.io/docs/deployment/troubleshooting) may be helpful if you face any issues. +The [troubleshooting instructions](https://signoz.io/docs/install/troubleshooting/) may be helpful if you face any issues.

 

diff --git a/README.pt-br.md b/README.pt-br.md index ce168b4101..c817e8afb9 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -84,9 +84,9 @@ Você pode encontrar a lista completa de linguagens aqui - https://opentelemetry ### Implantar usando Docker -Siga as etapas listadas [aqui](https://signoz.io/docs/deployment/docker/) para instalar usando o Docker. +Siga as etapas listadas [aqui](https://signoz.io/docs/install/docker/) para instalar usando o Docker. -Esse [guia para solução de problemas](https://signoz.io/docs/deployment/troubleshooting) pode ser útil se você enfrentar quaisquer problemas. +Esse [guia para solução de problemas](https://signoz.io/docs/install/troubleshooting/) pode ser útil se você enfrentar quaisquer problemas.

 

diff --git a/README.zh-cn.md b/README.zh-cn.md index 3658eeb520..aaa89551bf 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -80,9 +80,9 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo ### 使用Docker部署 -请按照[这里](https://signoz.io/docs/deployment/docker/)列出的步骤使用Docker来安装 +请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装 -如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/deployment/troubleshooting)会对你有帮助。 +如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。

 

diff --git a/deploy/install.sh b/deploy/install.sh index e8a14a5821..e908dd8952 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -125,7 +125,7 @@ check_ports_occupied() { echo "+++++++++++ ERROR ++++++++++++++++++++++" echo "SigNoz requires ports 3301 & 4317 to be open. Please shut down any other service(s) that may be running on these ports." - echo "You can run SigNoz on another port following this guide https://signoz.io/docs/deployment/docker#troubleshooting" + echo "You can run SigNoz on another port following this guide https://signoz.io/docs/install/troubleshooting/" echo "++++++++++++++++++++++++++++++++++++++++" echo "" exit 1 @@ -249,7 +249,7 @@ bye() { # Prints a friendly good bye message and exits the script. echo "" echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a" - # echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker#troubleshooting" + echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/" echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack" echo "++++++++++++++++++++++++++++++++++++++++" @@ -500,7 +500,7 @@ if [[ $status_code -ne 200 ]]; then echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a" - echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker/#troubleshooting-of-common-issues" + echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/" echo "or reach us on SigNoz for support https://signoz.io/slack" echo "++++++++++++++++++++++++++++++++++++++++" From 502b8b1ba8e9387ba2a1b86585370a40afd9b7ed Mon Sep 17 00:00:00 2001 From: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Date: Tue, 18 Apr 2023 11:28:44 +0530 Subject: [PATCH 20/90] fix: tag filters query missing on page reload (#2580) * fix: remove frontend code owner * chore: set Cache-Control for auto complete requests (#2504) * feat(filter): add group by filter (#2538) * fix: tag filters query missing on page reload --------- Co-authored-by: Srikanth Chekuri Co-authored-by: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> --- frontend/src/container/Trace/Search/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx index d9b4741082..099059ac80 100644 --- a/frontend/src/container/Trace/Search/index.tsx +++ b/frontend/src/container/Trace/Search/index.tsx @@ -27,7 +27,7 @@ function Search({ const dispatch = useDispatch>(); useEffect(() => { - if (traces.filterLoading) { + if (!traces.filterLoading) { const initialTags = parseTagsToQuery(traces.selectedTags); if (!initialTags.isError) { setValue(initialTags.payload); From 51f1d0fd05a057cd347b049f5d39b8b836f491a3 Mon Sep 17 00:00:00 2001 From: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Date: Tue, 18 Apr 2023 11:44:28 +0530 Subject: [PATCH 21/90] feat: added aggregate every filter (#2581) * fix: remove frontend code owner * chore: set Cache-Control for auto complete requests (#2504) * feat(filter): add group by filter (#2538) * feat: added aggregate every filter --------- Co-authored-by: Srikanth Chekuri Co-authored-by: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> --- .../QueryBuilder/components/Query/Query.tsx | 23 +++++++ .../filters/AggregateEveryFilter/index.tsx | 62 +++++++++++++++++++ .../filters/QueryBuilderSearch/index.tsx | 2 +- .../filters/QueryBuilderSearch/style.ts | 4 +- 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index d6b1837081..bccae6d46b 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -19,6 +19,7 @@ import { OperatorsSelect, ReduceToFilter, } from 'container/QueryBuilder/filters'; +import AggregateEveryFilter from 'container/QueryBuilder/filters/AggregateEveryFilter'; import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import { useQueryBuilder } from 'hooks/useQueryBuilder'; @@ -204,6 +205,17 @@ export const Query = memo(function Query({ [index, query, handleSetQueryData], ); + const handleChangeAggregateEvery = useCallback( + (value: number): void => { + const newQuery: IBuilderQueryForm = { + ...query, + stepInterval: value, + }; + handleSetQueryData(index, newQuery); + }, + [index, query, handleSetQueryData], + ); + return ( @@ -276,6 +288,17 @@ export const Query = memo(function Query({
)} + + + + + + + + diff --git a/frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx b/frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx new file mode 100644 index 0000000000..027fabeec4 --- /dev/null +++ b/frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx @@ -0,0 +1,62 @@ +import { Input } from 'antd'; +import getStep from 'lib/getStep'; +import React, { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { selectStyle } from '../QueryBuilderSearch/config'; + +function AggregateEveryFilter({ + onChange, + query, +}: AggregateEveryFilterProps): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const stepInterval = useMemo( + () => + getStep({ + start: minTime, + end: maxTime, + inputFormat: 'ns', + }), + [maxTime, minTime], + ); + + const handleKeyDown = (event: { + keyCode: number; + which: number; + preventDefault: () => void; + }): void => { + const keyCode = event.keyCode || event.which; + const isBackspace = keyCode === 8; + const isNumeric = + (keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105); + + if (!isNumeric && !isBackspace) { + event.preventDefault(); + } + }; + + return ( + onChange(Number(event.target.value))} + onKeyDown={handleKeyDown} + /> + ); +} + +interface AggregateEveryFilterProps { + onChange: (values: number) => void; + query: IBuilderQueryForm; +} + +export default AggregateEveryFilter; diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index d08345b47c..f31e813fc5 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -35,7 +35,7 @@ function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element { return ( - + {value} diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts index 064392b773..5969c0f1a7 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts @@ -2,8 +2,8 @@ import { CheckOutlined } from '@ant-design/icons'; import { Typography } from 'antd'; import styled from 'styled-components'; -export const TypographyText = styled(Typography.Text)<{ isInNin: boolean }>` - width: ${({ isInNin }): string => (isInNin ? '10rem' : 'auto')}; +export const TypographyText = styled(Typography.Text)<{ $isInNin: boolean }>` + width: ${({ $isInNin }): string => ($isInNin ? '10rem' : 'auto')}; `; export const StyledCheckOutlined = styled(CheckOutlined)` From 60b78e94d84b8e0c837002448b1ca031869ddeaf Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Tue, 18 Apr 2023 16:38:52 +0530 Subject: [PATCH 22/90] fix: add support for count aggregate attribute (#2584) --- pkg/query-service/app/clickhouseReader/reader.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 1b0b771139..0dd0e1e77a 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -3806,7 +3806,9 @@ func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v where := "" switch req.Operator { - case v3.AggregateOperatorCountDistinct: + case + v3.AggregateOperatorCountDistinct, + v3.AggregateOpeatorCount: where = "tagKey ILIKE $1" case v3.AggregateOperatorRateSum, @@ -3829,7 +3831,6 @@ func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v v3.AggregateOperatorMax: where = "tagKey ILIKE $1 AND (tagDataType='int64' or tagDataType='float64')" case - v3.AggregateOpeatorCount, v3.AggregateOperatorNoOp: return &v3.AggregateAttributeResponse{}, nil default: @@ -4228,7 +4229,9 @@ func (r *ClickHouseReader) GetTraceAggregateAttributes(ctx context.Context, req var response v3.AggregateAttributeResponse where := "" switch req.Operator { - case v3.AggregateOperatorCountDistinct: + case + v3.AggregateOperatorCountDistinct, + v3.AggregateOpeatorCount: where = "tagKey ILIKE $1" case v3.AggregateOperatorRateSum, @@ -4251,7 +4254,6 @@ func (r *ClickHouseReader) GetTraceAggregateAttributes(ctx context.Context, req v3.AggregateOperatorMax: where = "tagKey ILIKE $1 AND dataType='float64'" case - v3.AggregateOpeatorCount, v3.AggregateOperatorNoOp: return &v3.AggregateAttributeResponse{}, nil default: From 63570c847a5fdbea5792c3eaa94c5e3cf93cc9bb Mon Sep 17 00:00:00 2001 From: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:17:06 +0530 Subject: [PATCH 23/90] feat: added Order By filter (#2551) * fix: remove frontend code owner * chore: set Cache-Control for auto complete requests (#2504) * feat(filter): add group by filter (#2538) * feat: poc of search bar * feat: poc of search bar * feat: attribute keys auto complete api * chore: conflict resolve * chore: conflict resolve * fix: menu was not open on click * feat: re-used antoney's hooks code * fix: linting & type issue * fix: unwanted file changes * fix: conflic changes * feat: added orderby filter * chore: rebased changes * feat: poc of search bar * feat: poc of search bar * feat: attribute keys auto complete api * chore: conflict resolve * fix: menu was not open on click * feat: re-used antoney's hooks code * fix: linting & type issue * fix: uncomment qb component * fix: unwanted file changes * fix: conflic changes * fix: suggested changes * fix: reused label component * fix: unwanted changes * fix: unwanted changes * fix: recovered old changes * fix: orderby reset behaviour * chore: rebased changes * fix: resolved unwanted changes * fix: ui of filter row * fix: resolved order by filter issue on label * fix: resolved reset behaviour --------- Co-authored-by: Srikanth Chekuri Co-authored-by: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Co-authored-by: Palash Gupta --- frontend/src/constants/queryBuilder.ts | 2 +- .../AdditionalFiltersToggler.tsx | 3 +- .../components/Query/Query.styled.ts | 4 + .../QueryBuilder/components/Query/Query.tsx | 55 +++++-- .../filters/GroupByFilter/GroupByFilter.tsx | 17 ++- .../filters/LimitFilter/LimitFilter.tsx | 13 +- .../OperatorsSelect/OperatorsSelect.tsx | 3 +- .../OrderByFilter/OrderByFilter.interfaces.ts | 15 ++ .../filters/OrderByFilter/OrderByFilter.tsx | 141 ++++++++++++++++++ .../filters/OrderByFilter/index.ts | 1 + .../filters/OrderByFilter/utils.ts | 32 ++++ .../filters/QueryBuilderSearch/index.tsx | 36 ++++- .../src/hooks/queryBuilder/useAutoComplete.ts | 2 +- frontend/src/hooks/queryBuilder/useOptions.ts | 2 +- .../api/queryBuilder/queryBuilderData.ts | 7 +- 15 files changed, 294 insertions(+), 39 deletions(-) create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces.ts create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts create mode 100644 frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts 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 (