feat(filter): add group by filter (#2538)

This commit is contained in:
Yevhen Shevchenko 2023-04-05 12:32:15 +03:00 committed by GitHub
parent d4bfe3a096
commit 5f73a82d9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 197 additions and 45 deletions

View File

@ -11,6 +11,7 @@ export const getAggregateKeys = async ({
searchText, searchText,
dataSource, dataSource,
aggregateAttribute, aggregateAttribute,
tagType,
}: IGetAttributeKeysPayload): Promise< }: IGetAttributeKeysPayload): Promise<
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
> => { > => {
@ -18,7 +19,7 @@ export const getAggregateKeys = async ({
const response: AxiosResponse<{ const response: AxiosResponse<{
data: IQueryAutocompleteResponse; data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get( }> = 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 { return {

View File

@ -1,3 +1,4 @@
export enum QueryBuilderKeys { export enum QueryBuilderKeys {
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE', GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
} }

View File

@ -2,7 +2,6 @@ import styled from 'styled-components';
export const StyledLabel = styled.div` export const StyledLabel = styled.div`
padding: 0 0.6875rem; padding: 0 0.6875rem;
min-width: 6.5rem;
width: fit-content; width: fit-content;
min-height: 2rem; min-height: 2rem;
display: inline-flex; display: inline-flex;

View File

@ -1,9 +1,12 @@
import { Button } from 'antd'; import { Button } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
export const StyledButton = styled(Button)` export const StyledButton = styled(Button)<{ isAvailableToDisable: boolean }>`
min-width: 2rem; min-width: 2rem;
height: 2.25rem; height: 2.25rem;
padding: 0.125rem; padding: 0.125rem;
border-radius: 0.375rem; border-radius: 0.375rem;
margin-right: 0.1rem;
pointer-events: ${(props): string =>
props.isAvailableToDisable ? 'default' : 'none'};
`; `;

View File

@ -30,7 +30,8 @@ export function ListMarker({
icon={buttonProps.icon} icon={buttonProps.icon}
onClick={buttonProps.onClick} onClick={buttonProps.onClick}
className={className} className={className}
style={{ marginRight: '0.1rem', ...style }} isAvailableToDisable={isAvailableToDisable}
style={style}
> >
{labelName} {labelName}
</StyledButton> </StyledButton>

View File

@ -8,13 +8,14 @@ import {
} from 'container/QueryBuilder/components'; } from 'container/QueryBuilder/components';
import { import {
AggregatorFilter, AggregatorFilter,
GroupByFilter,
OperatorsSelect, OperatorsSelect,
} from 'container/QueryBuilder/filters'; } from 'container/QueryBuilder/filters';
// Context // Context
import { useQueryBuilder } from 'hooks/useQueryBuilder'; import { useQueryBuilder } from 'hooks/useQueryBuilder';
// ** Hooks // ** Hooks
import React from 'react'; import React from 'react';
import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
// ** Constants // ** Constants
import { import {
@ -55,40 +56,66 @@ export function Query({
handleSetQueryData(index, { disabled: !query.disabled }); handleSetQueryData(index, { disabled: !query.disabled });
}; };
const handleChangeAggregatorAttribute = (value: AutocompleteData): void => { const handleChangeAggregatorAttribute = (
value: BaseAutocompleteData,
): void => {
handleSetQueryData(index, { aggregateAttribute: value }); handleSetQueryData(index, { aggregateAttribute: value });
}; };
const handleChangeGroupByKeys = (values: BaseAutocompleteData[]): void => {
handleSetQueryData(index, { groupBy: values });
};
return ( return (
<Row style={{ gap: '0.75rem' }}> <Row gutter={[0, 15]}>
<Col span={12}>
<ListMarker
isDisabled={query.disabled}
toggleDisabled={handleToggleDisableQuery}
labelName={query.queryName}
index={index}
isAvailableToDisable={isAvailableToDisable}
/>
{queryVariant === 'dropdown' ? (
<DataSourceDropdown
onChange={handleChangeDataSource}
value={query.dataSource}
/>
) : (
<FilterLabel label={transformToUpperCase(query.dataSource)} />
)}
</Col>
<Col span={24}> <Col span={24}>
<OperatorsSelect <Row wrap={false} align="middle">
value={query.aggregateOperator || currentListOfOperators[0]} <Col span={24}>
onChange={handleChangeOperator} <ListMarker
operators={currentListOfOperators} isDisabled={query.disabled}
style={{ minWidth: 104, marginRight: '0.75rem' }} toggleDisabled={handleToggleDisableQuery}
/> labelName={query.queryName}
<AggregatorFilter index={index}
onChange={handleChangeAggregatorAttribute} isAvailableToDisable={isAvailableToDisable}
query={query} />
/> {queryVariant === 'dropdown' ? (
<DataSourceDropdown
onChange={handleChangeDataSource}
value={query.dataSource}
/>
) : (
<FilterLabel label={transformToUpperCase(query.dataSource)} />
)}
{/* TODO: here will be search */}
</Col>
</Row>
</Col>
<Col span={11}>
<Row gutter={[11, 5]}>
<Col flex="95px">
<OperatorsSelect
value={query.aggregateOperator || currentListOfOperators[0]}
onChange={handleChangeOperator}
operators={currentListOfOperators}
/>
</Col>
<Col flex="1 1 200px">
<AggregatorFilter
onChange={handleChangeAggregatorAttribute}
query={query}
/>
</Col>
</Row>
</Col>
<Col span={11} offset={2}>
<Row gutter={[11, 5]}>
<Col flex="95px">
<FilterLabel label="Group by" />
</Col>
<Col flex="1 1 200px">
<GroupByFilter query={query} onChange={handleChangeGroupByKeys} />
</Col>
</Row>
</Col> </Col>
</Row> </Row>
); );

View File

@ -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'; import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
export type AgregatorFilterProps = { export type AgregatorFilterProps = {
onChange: (value: AutocompleteData) => void; onChange: (value: BaseAutocompleteData) => void;
query: IBuilderQueryForm; query: IBuilderQueryForm;
}; };

View File

@ -68,10 +68,8 @@ export function AggregatorFilter({
return ( return (
<AutoComplete <AutoComplete
showSearch showSearch
placeholder={`${transformToUpperCase( placeholder={`${transformToUpperCase(query.dataSource)} name`}
query.dataSource, style={{ width: '100%' }}
)} Name (Start typing to get suggestions)`}
style={{ flex: 1, minWidth: 200 }}
showArrow={false} showArrow={false}
filterOption={false} filterOption={false}
onSearch={handleSearchAttribute} onSearch={handleSearchAttribute}

View File

@ -0,0 +1,15 @@
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
export type GroupByFilterProps = {
query: IBuilderQueryForm;
onChange: (values: BaseAutocompleteData[]) => void;
};
export type GroupByFilterValue = {
disabled: boolean | undefined;
key: string;
label: string;
title: string | undefined;
value: string;
};

View File

@ -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<string>('');
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<string, string>[] =
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 (
<Select
mode="tags"
style={{ width: '100%' }}
onSearch={handleSearchKeys}
showSearch
disabled={!query.aggregateAttribute.key}
showArrow={false}
filterOption={false}
options={optionsData}
labelInValue
value={values}
notFoundContent={isFetching ? <Spin size="small" /> : null}
onChange={handleChange}
/>
);
}

View File

@ -0,0 +1 @@
export { GroupByFilter } from './GroupByFilter';

View File

@ -25,6 +25,7 @@ export function OperatorsSelect({
options={operatorsOptions} options={operatorsOptions}
value={value} value={value}
onChange={onChange} onChange={onChange}
style={{ width: '100%' }}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...props} {...props}
/> />

View File

@ -1,2 +1,3 @@
export { AggregatorFilter } from './AggregatorFilter'; export { AggregatorFilter } from './AggregatorFilter';
export { GroupByFilter } from './GroupByFilter';
export { OperatorsSelect } from './OperatorsSelect'; export { OperatorsSelect } from './OperatorsSelect';

View File

@ -53,6 +53,7 @@ export function QueryBuilderProvider({
isColumn: null, isColumn: null,
type: null, type: null,
}, },
groupBy: [],
}, },
], ],
queryFormulas: [], queryFormulas: [],

View File

@ -1,8 +1,11 @@
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { BaseAutocompleteData } from './queryAutocompleteResponse';
export interface IGetAttributeKeysPayload { export interface IGetAttributeKeysPayload {
aggregateOperator: string; aggregateOperator: string;
dataSource: DataSource; dataSource: DataSource;
searchText: string; searchText: string;
aggregateAttribute: string; aggregateAttribute: string;
tagType: BaseAutocompleteData['type'];
} }

View File

@ -1,4 +1,4 @@
export interface AutocompleteData { export interface BaseAutocompleteData {
dataType: 'number' | 'string' | 'boolean' | null; dataType: 'number' | 'string' | 'boolean' | null;
isColumn: boolean | null; isColumn: boolean | null;
key: string; key: string;
@ -6,5 +6,5 @@ export interface AutocompleteData {
} }
export interface IQueryAutocompleteResponse { export interface IQueryAutocompleteResponse {
attributeKeys: AutocompleteData[]; attributeKeys: BaseAutocompleteData[];
} }

View File

@ -1,6 +1,6 @@
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { AutocompleteData } from './queryAutocompleteResponse'; import { BaseAutocompleteData } from './queryAutocompleteResponse';
// Type for Formula // Type for Formula
export interface IBuilderFormula { export interface IBuilderFormula {
@ -30,7 +30,7 @@ export type IBuilderQuery = {
aggregateOperator: string; aggregateOperator: string;
aggregateAttribute: string; aggregateAttribute: string;
tagFilters: TagFilter[]; tagFilters: TagFilter[];
groupBy: string[]; groupBy: BaseAutocompleteData[];
expression: string; expression: string;
disabled: boolean; disabled: boolean;
having?: string; having?: string;
@ -40,5 +40,5 @@ export type IBuilderQuery = {
}; };
export type IBuilderQueryForm = Omit<IBuilderQuery, 'aggregateAttribute'> & { export type IBuilderQueryForm = Omit<IBuilderQuery, 'aggregateAttribute'> & {
aggregateAttribute: AutocompleteData; aggregateAttribute: BaseAutocompleteData;
}; };