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,
dataSource,
aggregateAttribute,
tagType,
}: IGetAttributeKeysPayload): Promise<
SuccessResponse<IQueryAutocompleteResponse> | 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 {

View File

@ -1,3 +1,4 @@
export enum QueryBuilderKeys {
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`
padding: 0 0.6875rem;
min-width: 6.5rem;
width: fit-content;
min-height: 2rem;
display: inline-flex;

View File

@ -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'};
`;

View File

@ -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}
</StyledButton>

View File

@ -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 (
<Row style={{ gap: '0.75rem' }}>
<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>
<Row gutter={[0, 15]}>
<Col span={24}>
<OperatorsSelect
value={query.aggregateOperator || currentListOfOperators[0]}
onChange={handleChangeOperator}
operators={currentListOfOperators}
style={{ minWidth: 104, marginRight: '0.75rem' }}
/>
<AggregatorFilter
onChange={handleChangeAggregatorAttribute}
query={query}
/>
<Row wrap={false} align="middle">
<Col span={24}>
<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)} />
)}
{/* 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>
</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';
export type AgregatorFilterProps = {
onChange: (value: AutocompleteData) => void;
onChange: (value: BaseAutocompleteData) => void;
query: IBuilderQueryForm;
};

View File

@ -68,10 +68,8 @@ export function AggregatorFilter({
return (
<AutoComplete
showSearch
placeholder={`${transformToUpperCase(
query.dataSource,
)} Name (Start typing to get suggestions)`}
style={{ flex: 1, minWidth: 200 }}
placeholder={`${transformToUpperCase(query.dataSource)} name`}
style={{ width: '100%' }}
showArrow={false}
filterOption={false}
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}
value={value}
onChange={onChange}
style={{ width: '100%' }}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>

View File

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

View File

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

View File

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

View File

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

View File

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