mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 14:08:59 +08:00
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 <srikanth.chekuri92@gmail.com> Co-authored-by: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
60b78e94d8
commit
63570c847a
@ -44,7 +44,7 @@ export const initialQueryBuilderFormValues: IBuilderQueryForm = {
|
|||||||
queryName: createNewQueryName([]),
|
queryName: createNewQueryName([]),
|
||||||
aggregateOperator: Object.values(MetricAggregateOperator)[0],
|
aggregateOperator: Object.values(MetricAggregateOperator)[0],
|
||||||
aggregateAttribute: initialAggregateAttribute,
|
aggregateAttribute: initialAggregateAttribute,
|
||||||
tagFilters: [],
|
tagFilters: { items: [], op: 'AND' },
|
||||||
expression: '',
|
expression: '',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
having: [],
|
having: [],
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Row } from 'antd';
|
|
||||||
import React, { Fragment, memo, ReactNode, useState } from 'react';
|
import React, { Fragment, memo, ReactNode, useState } from 'react';
|
||||||
|
|
||||||
// ** Types
|
// ** Types
|
||||||
@ -45,7 +44,7 @@ export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({
|
|||||||
{isOpenedFilters ? <StyledIconClose /> : <StyledIconOpen />}
|
{isOpenedFilters ? <StyledIconClose /> : <StyledIconOpen />}
|
||||||
{!isOpenedFilters && <span>Add conditions for {filtersTexts}</span>}
|
{!isOpenedFilters && <span>Add conditions for {filtersTexts}</span>}
|
||||||
</StyledInner>
|
</StyledInner>
|
||||||
{isOpenedFilters && <Row>{children}</Row>}
|
{isOpenedFilters && children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -20,3 +20,7 @@ export const StyledDeleteEntity = styled(CloseCircleOutlined)`
|
|||||||
export const StyledRow = styled(Row)`
|
export const StyledRow = styled(Row)`
|
||||||
padding-right: 3rem;
|
padding-right: 3rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const StyledFilterRow = styled(Row)`
|
||||||
|
margin-bottom: 0.875rem;
|
||||||
|
`;
|
||||||
|
@ -21,20 +21,24 @@ import {
|
|||||||
} from 'container/QueryBuilder/filters';
|
} from 'container/QueryBuilder/filters';
|
||||||
import AggregateEveryFilter from 'container/QueryBuilder/filters/AggregateEveryFilter';
|
import AggregateEveryFilter from 'container/QueryBuilder/filters/AggregateEveryFilter';
|
||||||
import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter';
|
import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter';
|
||||||
|
import { OrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter';
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||||
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
||||||
import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
|
import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
|
||||||
// ** Hooks
|
// ** Hooks
|
||||||
import React, { memo, useCallback, useMemo } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
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 { DataSource } from 'types/common/queryBuilder';
|
||||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||||
|
|
||||||
// ** Types
|
// ** Types
|
||||||
import { QueryProps } from './Query.interfaces';
|
import { QueryProps } from './Query.interfaces';
|
||||||
// ** Styles
|
// ** Styles
|
||||||
import { StyledDeleteEntity, StyledRow } from './Query.styled';
|
import { StyledDeleteEntity, StyledFilterRow, StyledRow } from './Query.styled';
|
||||||
|
|
||||||
export const Query = memo(function Query({
|
export const Query = memo(function Query({
|
||||||
index,
|
index,
|
||||||
@ -66,10 +70,13 @@ export const Query = memo(function Query({
|
|||||||
...query,
|
...query,
|
||||||
aggregateOperator: value,
|
aggregateOperator: value,
|
||||||
having: [],
|
having: [],
|
||||||
|
groupBy: [],
|
||||||
|
orderBy: [],
|
||||||
limit: null,
|
limit: null,
|
||||||
|
tagFilters: { items: [], op: 'AND' },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!aggregateDataType || query.dataSource === DataSource.METRICS) {
|
if (!aggregateDataType) {
|
||||||
handleSetQueryData(index, newQuery);
|
handleSetQueryData(index, newQuery);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -194,6 +201,17 @@ export const Query = memo(function Query({
|
|||||||
[query.dataSource],
|
[query.dataSource],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleChangeOrderByKeys = useCallback(
|
||||||
|
(values: BaseAutocompleteData[]): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
orderBy: values,
|
||||||
|
};
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[handleSetQueryData, index, query],
|
||||||
|
);
|
||||||
|
|
||||||
const handleChangeLimit = useCallback(
|
const handleChangeLimit = useCallback(
|
||||||
(value: number | null): void => {
|
(value: number | null): void => {
|
||||||
const newQuery: IBuilderQueryForm = {
|
const newQuery: IBuilderQueryForm = {
|
||||||
@ -216,6 +234,17 @@ export const Query = memo(function Query({
|
|||||||
[index, query, handleSetQueryData],
|
[index, query, handleSetQueryData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleChangeTagFilters = useCallback(
|
||||||
|
(value: TagFilter): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
tagFilters: value,
|
||||||
|
};
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[index, query, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRow gutter={[0, 15]}>
|
<StyledRow gutter={[0, 15]}>
|
||||||
<StyledDeleteEntity onClick={handleDeleteQuery} />
|
<StyledDeleteEntity onClick={handleDeleteQuery} />
|
||||||
@ -241,7 +270,7 @@ export const Query = memo(function Query({
|
|||||||
{isMatricsDataSource && <FilterLabel label="WHERE" />}
|
{isMatricsDataSource && <FilterLabel label="WHERE" />}
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={isMatricsDataSource ? 17 : 20}>
|
<Col span={isMatricsDataSource ? 17 : 20}>
|
||||||
<QueryBuilderSearch query={query} />
|
<QueryBuilderSearch query={query} onChange={handleChangeTagFilters} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
@ -279,20 +308,26 @@ export const Query = memo(function Query({
|
|||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<AdditionalFiltersToggler listOfAdditionalFilter={listOfAdditionalFilters}>
|
<AdditionalFiltersToggler listOfAdditionalFilter={listOfAdditionalFilters}>
|
||||||
{!isMatricsDataSource && (
|
{!isMatricsDataSource && (
|
||||||
<Row gutter={[11, 5]}>
|
<StyledFilterRow gutter={[11, 5]} justify="space-around">
|
||||||
<Col span={6}>
|
<Col span={2}>
|
||||||
|
<FilterLabel label="Order by" />
|
||||||
|
</Col>
|
||||||
|
<Col span={10}>
|
||||||
|
<OrderByFilter query={query} onChange={handleChangeOrderByKeys} />
|
||||||
|
</Col>
|
||||||
|
<Col span={1.5}>
|
||||||
<FilterLabel label="Limit" />
|
<FilterLabel label="Limit" />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={18}>
|
<Col span={10}>
|
||||||
<LimitFilter query={query} onChange={handleChangeLimit} />
|
<LimitFilter query={query} onChange={handleChangeLimit} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</StyledFilterRow>
|
||||||
)}
|
)}
|
||||||
<Row gutter={[11, 5]}>
|
<Row gutter={[11, 5]}>
|
||||||
<Col span={10}>
|
<Col span={3}>
|
||||||
<FilterLabel label="Aggregate Every" />
|
<FilterLabel label="Aggregate Every" />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={14}>
|
<Col span={8}>
|
||||||
<AggregateEveryFilter
|
<AggregateEveryFilter
|
||||||
query={query}
|
query={query}
|
||||||
onChange={handleChangeAggregateEvery}
|
onChange={handleChangeAggregateEvery}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { Select, Spin } from 'antd';
|
import { Select, Spin } from 'antd';
|
||||||
// ** Api
|
|
||||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
// ** Constants
|
// ** Constants
|
||||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
// ** Components
|
// ** Components
|
||||||
// ** Helpers
|
// ** Helpers
|
||||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||||
import React, { memo, useState } from 'react';
|
import React, { memo, useMemo, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
|
|
||||||
// ** Types
|
import { selectStyle } from '../QueryBuilderSearch/config';
|
||||||
import {
|
import {
|
||||||
GroupByFilterProps,
|
GroupByFilterProps,
|
||||||
GroupByFilterValue,
|
GroupByFilterValue,
|
||||||
@ -81,13 +81,20 @@ export const GroupByFilter = memo(function GroupByFilter({
|
|||||||
title: undefined,
|
title: undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const isDisabledSelect = useMemo(
|
||||||
|
() =>
|
||||||
|
!query.aggregateAttribute.key ||
|
||||||
|
query.aggregateOperator === MetricAggregateOperator.NOOP,
|
||||||
|
[query.aggregateAttribute.key, query.aggregateOperator],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
mode="tags"
|
mode="tags"
|
||||||
style={{ width: '100%' }}
|
style={selectStyle}
|
||||||
onSearch={handleSearchKeys}
|
onSearch={handleSearchKeys}
|
||||||
showSearch
|
showSearch
|
||||||
disabled={!query.aggregateAttribute.key}
|
disabled={isDisabledSelect}
|
||||||
showArrow={false}
|
showArrow={false}
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
options={optionsData}
|
options={optionsData}
|
||||||
|
@ -1,30 +1,21 @@
|
|||||||
import { InputNumber } from 'antd';
|
import { InputNumber } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import { selectStyle } from '../QueryBuilderSearch/config';
|
import { selectStyle } from '../QueryBuilderSearch/config';
|
||||||
|
|
||||||
function LimitFilter({ onChange, query }: LimitFilterProps): JSX.Element {
|
function LimitFilter({ onChange, query }: LimitFilterProps): JSX.Element {
|
||||||
const [isData, setIsData] = useState<number | null>(null);
|
|
||||||
const onChangeHandler = (value: number | null): void => {
|
const onChangeHandler = (value: number | null): void => {
|
||||||
setIsData(value);
|
onChange(value);
|
||||||
};
|
|
||||||
|
|
||||||
const handleEnter = (e: { key: string }): void => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
onChange(isData);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={1}
|
min={1}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="e.g 10"
|
|
||||||
disabled={!query.aggregateAttribute.key}
|
disabled={!query.aggregateAttribute.key}
|
||||||
style={selectStyle}
|
style={selectStyle}
|
||||||
onChange={onChangeHandler}
|
onChange={onChangeHandler}
|
||||||
onPressEnter={handleEnter}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { SelectOption } from 'types/common/select';
|
|||||||
// ** Helpers
|
// ** Helpers
|
||||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||||
|
|
||||||
|
import { selectStyle } from '../QueryBuilderSearch/config';
|
||||||
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
|
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
|
||||||
|
|
||||||
export const OperatorsSelect = memo(function OperatorsSelect({
|
export const OperatorsSelect = memo(function OperatorsSelect({
|
||||||
@ -25,7 +26,7 @@ export const OperatorsSelect = memo(function OperatorsSelect({
|
|||||||
options={operatorsOptions}
|
options={operatorsOptions}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
style={{ width: '100%' }}
|
style={selectStyle}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export type OrderByFilterProps = {
|
||||||
|
query: IBuilderQueryForm;
|
||||||
|
onChange: (values: BaseAutocompleteData[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderByFilterValue = {
|
||||||
|
disabled: boolean | undefined;
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
title: string | undefined;
|
||||||
|
value: string;
|
||||||
|
};
|
@ -0,0 +1,141 @@
|
|||||||
|
import { Select, Spin } from 'antd';
|
||||||
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
|
import { IOption } from 'hooks/useResourceAttribute/types';
|
||||||
|
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { selectStyle } from '../QueryBuilderSearch/config';
|
||||||
|
import {
|
||||||
|
OrderByFilterProps,
|
||||||
|
OrderByFilterValue,
|
||||||
|
} from './OrderByFilter.interfaces';
|
||||||
|
import { getLabelFromValue, mapLabelValuePairs } from './utils';
|
||||||
|
|
||||||
|
export function OrderByFilter({
|
||||||
|
query,
|
||||||
|
onChange,
|
||||||
|
}: OrderByFilterProps): JSX.Element {
|
||||||
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
|
const [selectedValue, setSelectedValue] = useState<OrderByFilterValue[]>([]);
|
||||||
|
|
||||||
|
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 = useCallback(
|
||||||
|
(searchText: string): void => setSearchText(searchText),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const generateOptionsData = (
|
||||||
|
attributeKeys: BaseAutocompleteData[] | undefined,
|
||||||
|
selectedValue: OrderByFilterValue[],
|
||||||
|
query: IBuilderQueryForm,
|
||||||
|
): IOption[] => {
|
||||||
|
const selectedValueLabels = getLabelFromValue(selectedValue);
|
||||||
|
|
||||||
|
const noAggregationOptions = attributeKeys
|
||||||
|
? mapLabelValuePairs(attributeKeys)
|
||||||
|
.flat()
|
||||||
|
.filter(
|
||||||
|
(option) => !selectedValueLabels.includes(option.label.split(' ')[0]),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const aggregationOptions = mapLabelValuePairs(query.groupBy)
|
||||||
|
.flat()
|
||||||
|
.concat([
|
||||||
|
{
|
||||||
|
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) asc`,
|
||||||
|
value: `${query.aggregateOperator}(${query.aggregateAttribute.key}) asc`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) desc`,
|
||||||
|
value: `${query.aggregateOperator}(${query.aggregateAttribute.key}) desc`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.filter(
|
||||||
|
(option) => !selectedValueLabels.includes(option.label.split(' ')[0]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return query.aggregateOperator === MetricAggregateOperator.NOOP
|
||||||
|
? noAggregationOptions
|
||||||
|
: aggregationOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionsData = useMemo(
|
||||||
|
() => generateOptionsData(data?.payload?.attributeKeys, selectedValue, query),
|
||||||
|
[data?.payload?.attributeKeys, query, selectedValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChange = (values: OrderByFilterValue[]): void => {
|
||||||
|
setSelectedValue(values);
|
||||||
|
const orderByValues: BaseAutocompleteData[] = values?.map((item) => {
|
||||||
|
const iterationArray = data?.payload?.attributeKeys || query.orderBy;
|
||||||
|
const existingOrderValues = iterationArray.find(
|
||||||
|
(group) => group.key === item.value,
|
||||||
|
);
|
||||||
|
if (existingOrderValues) {
|
||||||
|
return existingOrderValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isColumn: null,
|
||||||
|
key: item.value,
|
||||||
|
dataType: null,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
onChange(orderByValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const values: OrderByFilterValue[] = query.orderBy.map((item) => ({
|
||||||
|
label: transformStringWithPrefix({
|
||||||
|
str: item.key,
|
||||||
|
prefix: item.type || '',
|
||||||
|
condition: !item.isColumn,
|
||||||
|
}),
|
||||||
|
key: item.key,
|
||||||
|
value: item.key,
|
||||||
|
disabled: undefined,
|
||||||
|
title: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const isDisabledSelect = useMemo(
|
||||||
|
() =>
|
||||||
|
!query.aggregateAttribute.key ||
|
||||||
|
query.aggregateOperator === MetricAggregateOperator.NOOP,
|
||||||
|
[query.aggregateAttribute.key, query.aggregateOperator],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
style={selectStyle}
|
||||||
|
onSearch={handleSearchKeys}
|
||||||
|
showSearch
|
||||||
|
disabled={isDisabledSelect}
|
||||||
|
showArrow={false}
|
||||||
|
filterOption={false}
|
||||||
|
options={optionsData}
|
||||||
|
labelInValue
|
||||||
|
value={values}
|
||||||
|
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { OrderByFilter } from './OrderByFilter';
|
@ -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<IOption>[] {
|
||||||
|
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]);
|
||||||
|
}
|
@ -1,13 +1,20 @@
|
|||||||
import { Select, Spin, Tag, Tooltip } from 'antd';
|
import { Select, Spin, Tag, Tooltip } from 'antd';
|
||||||
import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete';
|
import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete';
|
||||||
import React from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQueryForm,
|
||||||
|
TagFilter,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { selectStyle } from './config';
|
import { selectStyle } from './config';
|
||||||
import { StyledCheckOutlined, TypographyText } from './style';
|
import { StyledCheckOutlined, TypographyText } from './style';
|
||||||
import { isInNotInOperator } from './utils';
|
import { isInNotInOperator } from './utils';
|
||||||
|
|
||||||
function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element {
|
function QueryBuilderSearch({
|
||||||
|
query,
|
||||||
|
onChange,
|
||||||
|
}: QueryBuilderSearchProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
handleClearTag,
|
handleClearTag,
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
@ -51,6 +58,26 @@ function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element {
|
|||||||
if (isMulti || event.key === 'Backspace') handleKeyDown(event);
|
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 (
|
return (
|
||||||
<Select
|
<Select
|
||||||
virtual
|
virtual
|
||||||
@ -60,7 +87,7 @@ function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element {
|
|||||||
autoClearSearchValue={false}
|
autoClearSearchValue={false}
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="Search Filter"
|
placeholder="Search Filter"
|
||||||
value={tags}
|
value={queryTags}
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
disabled={!query.aggregateAttribute.key}
|
disabled={!query.aggregateAttribute.key}
|
||||||
style={selectStyle}
|
style={selectStyle}
|
||||||
@ -83,6 +110,7 @@ function QueryBuilderSearch({ query }: QueryBuilderSearchProps): JSX.Element {
|
|||||||
|
|
||||||
interface QueryBuilderSearchProps {
|
interface QueryBuilderSearchProps {
|
||||||
query: IBuilderQueryForm;
|
query: IBuilderQueryForm;
|
||||||
|
onChange: (value: TagFilter) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomTagProps {
|
export interface CustomTagProps {
|
||||||
|
@ -58,7 +58,7 @@ export const useAutoComplete = (query: IBuilderQueryForm): IAutoComplete => {
|
|||||||
}
|
}
|
||||||
return checkStringEndsWithSpace(prev)
|
return checkStringEndsWithSpace(prev)
|
||||||
? `${prev} ${value}`
|
? `${prev} ${value}`
|
||||||
: `${prev}, ${value}`;
|
: `${prev} ${value},`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!isMulti && isValidTag && !isExistsNotExistsOperator(value)) {
|
if (!isMulti && isValidTag && !isExistsNotExistsOperator(value)) {
|
||||||
|
@ -27,7 +27,7 @@ export const useOptions = (
|
|||||||
);
|
);
|
||||||
} else if (key && !operator) {
|
} else if (key && !operator) {
|
||||||
setOptions(
|
setOptions(
|
||||||
operators.map((o) => ({
|
operators?.map((o) => ({
|
||||||
value: `${key} ${o}`,
|
value: `${key} ${o}`,
|
||||||
label: `${key} ${o.replace('_', ' ')}`,
|
label: `${key} ${o.replace('_', ' ')}`,
|
||||||
})),
|
})),
|
||||||
|
@ -11,6 +11,7 @@ export interface IBuilderFormula {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TagFilterItem {
|
export interface TagFilterItem {
|
||||||
|
id: string;
|
||||||
key: string;
|
key: string;
|
||||||
// TODO: type it in the future
|
// TODO: type it in the future
|
||||||
op: string;
|
op: string;
|
||||||
@ -18,7 +19,7 @@ export interface TagFilterItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TagFilter {
|
export interface TagFilter {
|
||||||
items: TagFilterItem[];
|
items: TagFilterItem[] | [];
|
||||||
// TODO: type it in the future
|
// TODO: type it in the future
|
||||||
op: string;
|
op: string;
|
||||||
}
|
}
|
||||||
@ -35,14 +36,14 @@ export type IBuilderQuery = {
|
|||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
aggregateOperator: string;
|
aggregateOperator: string;
|
||||||
aggregateAttribute: string;
|
aggregateAttribute: string;
|
||||||
tagFilters: TagFilter[];
|
tagFilters: TagFilter;
|
||||||
groupBy: BaseAutocompleteData[];
|
groupBy: BaseAutocompleteData[];
|
||||||
expression: string;
|
expression: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
having: Having[];
|
having: Having[];
|
||||||
limit: number | null;
|
limit: number | null;
|
||||||
stepInterval: number;
|
stepInterval: number;
|
||||||
orderBy: string[];
|
orderBy: BaseAutocompleteData[];
|
||||||
reduceTo: string;
|
reduceTo: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user