mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 16:19:05 +08:00
feat(filter): add group by filter (#2538)
This commit is contained in:
parent
d4bfe3a096
commit
5f73a82d9f
@ -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 {
|
||||
|
@ -1,3 +1,4 @@
|
||||
export enum QueryBuilderKeys {
|
||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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'};
|
||||
`;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
};
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { GroupByFilter } from './GroupByFilter';
|
@ -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}
|
||||
/>
|
||||
|
@ -1,2 +1,3 @@
|
||||
export { AggregatorFilter } from './AggregatorFilter';
|
||||
export { GroupByFilter } from './GroupByFilter';
|
||||
export { OperatorsSelect } from './OperatorsSelect';
|
||||
|
@ -53,6 +53,7 @@ export function QueryBuilderProvider({
|
||||
isColumn: null,
|
||||
type: null,
|
||||
},
|
||||
groupBy: [],
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
|
@ -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'];
|
||||
}
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user