mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 18:49:06 +08:00
feat(builder): add additional filter toggler (#2549)
* feat(builder): add additional filter toggler * feat(builder): add filters deps from another --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
9ac2308b89
commit
7c952fd9cd
63
frontend/src/constants/queryBuilder.ts
Normal file
63
frontend/src/constants/queryBuilder.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// ** TODO: use it for creating formula names
|
||||||
|
// import { createNewFormulaName } from 'lib/newQueryBuilder/createNewFormulaName';
|
||||||
|
// ** Helpers
|
||||||
|
import { createNewQueryName } from 'lib/newQueryBuilder/createNewQueryName';
|
||||||
|
import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import {
|
||||||
|
BoolOperators,
|
||||||
|
DataSource,
|
||||||
|
LogsAggregatorOperator,
|
||||||
|
MetricAggregateOperator,
|
||||||
|
NumberOperators,
|
||||||
|
StringOperators,
|
||||||
|
TracesAggregatorOperator,
|
||||||
|
} from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
export enum QueryBuilderKeys {
|
||||||
|
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||||
|
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapOfOperators: Record<DataSource, string[]> = {
|
||||||
|
metrics: Object.values(MetricAggregateOperator),
|
||||||
|
logs: Object.values(LogsAggregatorOperator),
|
||||||
|
traces: Object.values(TracesAggregatorOperator),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapOfFilters: Record<DataSource, string[]> = {
|
||||||
|
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||||
|
metrics: ['Having', 'Aggregation interval'],
|
||||||
|
logs: ['Limit', 'Having', 'Order by', 'Aggregation interval'],
|
||||||
|
traces: ['Limit', 'Having', 'Order by', 'Aggregation interval'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialAggregateAttribute: IBuilderQueryForm['aggregateAttribute'] = {
|
||||||
|
dataType: null,
|
||||||
|
key: '',
|
||||||
|
isColumn: null,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialQueryBuilderFormValues: IBuilderQueryForm = {
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
queryName: createNewQueryName([]),
|
||||||
|
aggregateOperator: Object.values(MetricAggregateOperator)[0],
|
||||||
|
aggregateAttribute: initialAggregateAttribute,
|
||||||
|
tagFilters: [],
|
||||||
|
expression: '',
|
||||||
|
disabled: false,
|
||||||
|
having: [],
|
||||||
|
stepInterval: 30,
|
||||||
|
limit: 10,
|
||||||
|
orderBy: [],
|
||||||
|
groupBy: [],
|
||||||
|
legend: '',
|
||||||
|
reduceTo: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const operatorsByTypes: Record<LocalDataType, string[]> = {
|
||||||
|
string: Object.values(StringOperators),
|
||||||
|
number: Object.values(NumberOperators),
|
||||||
|
bool: Object.values(BoolOperators),
|
||||||
|
};
|
@ -1,4 +0,0 @@
|
|||||||
export enum QueryBuilderKeys {
|
|
||||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
|
||||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
|
||||||
}
|
|
@ -169,7 +169,7 @@ function QuerySection({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
// TODO: uncomment for testing new QueryBuilder
|
// TODO: uncomment for testing new QueryBuilder
|
||||||
// <QueryBuilder />
|
// <QueryBuilder panelType={selectedGraph} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
export type QueryBuilderConfig =
|
export type QueryBuilderConfig =
|
||||||
@ -9,4 +10,5 @@ export type QueryBuilderConfig =
|
|||||||
|
|
||||||
export type QueryBuilderProps = {
|
export type QueryBuilderProps = {
|
||||||
config?: QueryBuilderConfig;
|
config?: QueryBuilderConfig;
|
||||||
|
panelType?: ITEMS;
|
||||||
};
|
};
|
||||||
|
@ -1,35 +1,87 @@
|
|||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Col, Row } from 'antd';
|
||||||
// ** Hooks
|
// ** Hooks
|
||||||
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
||||||
import React from 'react';
|
import { MAX_FORMULAS } from 'lib/newQueryBuilder/createNewFormulaName';
|
||||||
|
// ** Constants
|
||||||
|
import { MAX_QUERIES } from 'lib/newQueryBuilder/createNewQueryName';
|
||||||
|
import React, { memo, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
// ** Components
|
// ** Components
|
||||||
import { Query } from './components';
|
import { Query } from './components';
|
||||||
// ** Types
|
// ** Types
|
||||||
import { QueryBuilderProps } from './QueryBuilder.interfaces';
|
import { QueryBuilderProps } from './QueryBuilder.interfaces';
|
||||||
|
// ** Styles
|
||||||
|
|
||||||
// TODO: I think it can be components switcher, because if we have different views based on the data source, we can render based on source
|
export const QueryBuilder = memo(function QueryBuilder({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
config,
|
||||||
export function QueryBuilder({ config }: QueryBuilderProps): JSX.Element {
|
panelType,
|
||||||
const { queryBuilderData } = useQueryBuilder();
|
}: QueryBuilderProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
queryBuilderData,
|
||||||
|
setupInitialDataSource,
|
||||||
|
addNewQuery,
|
||||||
|
} = useQueryBuilder();
|
||||||
|
|
||||||
// Here we can use Form from antd library and fill context data or edit
|
useEffect(() => {
|
||||||
// Connect form with adding or removing items from the list
|
if (config && config.queryVariant === 'static') {
|
||||||
|
setupInitialDataSource(config.initialDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
// Here will be map of query queryBuilderData.queryData and queryBuilderData.queryFormulas components
|
return (): void => {
|
||||||
// Each component can be part of antd Form list where we can add or remove items
|
setupInitialDataSource(null);
|
||||||
// Also need decide to make a copy of queryData for working with form or not and after it set the full new list with formulas or queries to the context
|
};
|
||||||
// With button to add him
|
}, [config, setupInitialDataSource]);
|
||||||
return (
|
|
||||||
<div>
|
const isDisabledQueryButton = useMemo(
|
||||||
{queryBuilderData.queryData.map((query, index) => (
|
() => queryBuilderData.queryData.length >= MAX_QUERIES,
|
||||||
<Query
|
[queryBuilderData],
|
||||||
key={query.queryName}
|
|
||||||
index={index}
|
|
||||||
isAvailableToDisable={queryBuilderData.queryData.length > 1}
|
|
||||||
queryVariant={config?.queryVariant || 'dropdown'}
|
|
||||||
query={query}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
const isDisabledFormulaButton = useMemo(
|
||||||
|
() => queryBuilderData.queryData.length >= MAX_FORMULAS,
|
||||||
|
[queryBuilderData],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={[0, 20]} justify="start">
|
||||||
|
<Col span={24}>
|
||||||
|
<Row gutter={[0, 50]}>
|
||||||
|
{queryBuilderData.queryData.map((query, index) => (
|
||||||
|
<Col key={query.queryName} span={24}>
|
||||||
|
<Query
|
||||||
|
index={index}
|
||||||
|
isAvailableToDisable={queryBuilderData.queryData.length > 1}
|
||||||
|
queryVariant={config?.queryVariant || 'dropdown'}
|
||||||
|
query={query}
|
||||||
|
panelType={panelType}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Row gutter={[20, 0]}>
|
||||||
|
<Col>
|
||||||
|
<Button
|
||||||
|
disabled={isDisabledQueryButton}
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={addNewQuery}
|
||||||
|
>
|
||||||
|
Query
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Button
|
||||||
|
disabled={isDisabledFormulaButton}
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
Formula
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
export type AdditionalFiltersProps = PropsWithChildren & {
|
||||||
|
listOfAdditionalFilter: string[];
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
const IconCss = css`
|
||||||
|
margin-right: 0.6875rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledIconOpen = styled(PlusSquareOutlined)`
|
||||||
|
${IconCss}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledIconClose = styled(MinusSquareOutlined)`
|
||||||
|
${IconCss}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: fit-content;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledInner = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.875rem;
|
||||||
|
min-height: 1.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
${StyledIconOpen}, ${StyledIconClose} {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledLink = styled(Typography.Link)`
|
||||||
|
pointer-events: none;
|
||||||
|
`;
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Row } from 'antd';
|
||||||
|
import React, { Fragment, memo, ReactNode, useState } from 'react';
|
||||||
|
|
||||||
|
// ** Types
|
||||||
|
import { AdditionalFiltersProps } from './AdditionalFiltersToggler.interfaces';
|
||||||
|
// ** Styles
|
||||||
|
import {
|
||||||
|
StyledIconClose,
|
||||||
|
StyledIconOpen,
|
||||||
|
StyledInner,
|
||||||
|
StyledLink,
|
||||||
|
StyledWrapper,
|
||||||
|
} from './AdditionalFiltersToggler.styled';
|
||||||
|
|
||||||
|
export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({
|
||||||
|
children,
|
||||||
|
listOfAdditionalFilter,
|
||||||
|
}: AdditionalFiltersProps): JSX.Element {
|
||||||
|
const [isOpenedFilters, setIsOpenedFilters] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleToggleOpenFilters = (): void => {
|
||||||
|
setIsOpenedFilters((prevState) => !prevState);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filtersTexts: ReactNode = listOfAdditionalFilter.map((str, index) => {
|
||||||
|
const isNextLast = index + 1 === listOfAdditionalFilter.length - 1;
|
||||||
|
if (index === listOfAdditionalFilter.length - 1) {
|
||||||
|
return (
|
||||||
|
<Fragment key={str}>
|
||||||
|
and <StyledLink>{str.toUpperCase()}</StyledLink>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={str}>
|
||||||
|
<StyledLink>{str.toUpperCase()}</StyledLink>
|
||||||
|
{isNextLast ? ' ' : ', '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<StyledInner onClick={handleToggleOpenFilters}>
|
||||||
|
{isOpenedFilters ? <StyledIconClose /> : <StyledIconOpen />}
|
||||||
|
{!isOpenedFilters && <span>Add conditions for {filtersTexts}</span>}
|
||||||
|
</StyledInner>
|
||||||
|
{isOpenedFilters && <Row>{children}</Row>}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1 @@
|
|||||||
|
export { AdditionalFiltersToggler } from './AdditionalFiltersToggler';
|
@ -1,5 +1,5 @@
|
|||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
// ** Helpers
|
// ** Helpers
|
||||||
@ -10,7 +10,9 @@ import { QueryLabelProps } from './DataSourceDropdown.interfaces';
|
|||||||
|
|
||||||
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
|
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
|
||||||
|
|
||||||
export function DataSourceDropdown(props: QueryLabelProps): JSX.Element {
|
export const DataSourceDropdown = memo(function DataSourceDropdown(
|
||||||
|
props: QueryLabelProps,
|
||||||
|
): JSX.Element {
|
||||||
const { onChange, value, style } = props;
|
const { onChange, value, style } = props;
|
||||||
|
|
||||||
const dataSourceOptions: SelectOption<
|
const dataSourceOptions: SelectOption<
|
||||||
@ -30,4 +32,4 @@ export function DataSourceDropdown(props: QueryLabelProps): JSX.Element {
|
|||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -5,6 +5,7 @@ export const StyledLabel = styled.div`
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
white-space: nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 0.125rem;
|
border-radius: 0.125rem;
|
||||||
border: 0.0625rem solid #434343;
|
border: 0.0625rem solid #434343;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
// ** Types
|
// ** Types
|
||||||
import { FilterLabelProps } from './FilterLabel.interfaces';
|
import { FilterLabelProps } from './FilterLabel.interfaces';
|
||||||
// ** Styles
|
// ** Styles
|
||||||
import { StyledLabel } from './FilterLabel.styled';
|
import { StyledLabel } from './FilterLabel.styled';
|
||||||
|
|
||||||
export function FilterLabel({ label }: FilterLabelProps): JSX.Element {
|
export const FilterLabel = memo(function FilterLabel({
|
||||||
|
label,
|
||||||
|
}: FilterLabelProps): JSX.Element {
|
||||||
return <StyledLabel>{label}</StyledLabel>;
|
return <StyledLabel>{label}</StyledLabel>;
|
||||||
}
|
});
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const StyledButton = styled(Button)<{ isAvailableToDisable: boolean }>`
|
export const StyledButton = styled(Button)<{ $isAvailableToDisable: boolean }>`
|
||||||
min-width: 2rem;
|
min-width: 2rem;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
padding: 0.125rem;
|
padding: ${(props): string =>
|
||||||
|
props.$isAvailableToDisable ? '0.43rem' : '0.43rem 0.68rem'};
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
margin-right: 0.1rem;
|
margin-right: 0.1rem;
|
||||||
pointer-events: ${(props): string =>
|
pointer-events: ${(props): string =>
|
||||||
props.isAvailableToDisable ? 'default' : 'none'};
|
props.$isAvailableToDisable ? 'default' : 'none'};
|
||||||
`;
|
`;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { EyeFilled, EyeInvisibleFilled } from '@ant-design/icons';
|
import { EyeFilled, EyeInvisibleFilled } from '@ant-design/icons';
|
||||||
import { ButtonProps } from 'antd';
|
import { ButtonProps } from 'antd';
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
// ** Types
|
// ** Types
|
||||||
import { ListMarkerProps } from './ListMarker.interfaces';
|
import { ListMarkerProps } from './ListMarker.interfaces';
|
||||||
// ** Styles
|
// ** Styles
|
||||||
import { StyledButton } from './ListMarker.styled';
|
import { StyledButton } from './ListMarker.styled';
|
||||||
|
|
||||||
export function ListMarker({
|
export const ListMarker = memo(function ListMarker({
|
||||||
isDisabled,
|
isDisabled,
|
||||||
labelName,
|
labelName,
|
||||||
index,
|
index,
|
||||||
@ -30,10 +30,10 @@ export function ListMarker({
|
|||||||
icon={buttonProps.icon}
|
icon={buttonProps.icon}
|
||||||
onClick={buttonProps.onClick}
|
onClick={buttonProps.onClick}
|
||||||
className={className}
|
className={className}
|
||||||
isAvailableToDisable={isAvailableToDisable}
|
$isAvailableToDisable={isAvailableToDisable}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{labelName}
|
{labelName}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
export type QueryProps = {
|
export type QueryProps = {
|
||||||
@ -5,4 +6,5 @@ export type QueryProps = {
|
|||||||
isAvailableToDisable: boolean;
|
isAvailableToDisable: boolean;
|
||||||
query: IBuilderQueryForm;
|
query: IBuilderQueryForm;
|
||||||
queryVariant: 'static' | 'dropdown';
|
queryVariant: 'static' | 'dropdown';
|
||||||
|
panelType?: ITEMS;
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { Row } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const StyledDeleteEntity = styled(CloseCircleOutlined)`
|
||||||
|
position: absolute;
|
||||||
|
top: 0.9375rem;
|
||||||
|
right: 0.9375rem;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.45;
|
||||||
|
width: 1.3125rem;
|
||||||
|
height: 1.3125rem;
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const StyledRow = styled(Row)`
|
||||||
|
padding-right: 3rem;
|
||||||
|
`;
|
@ -1,7 +1,14 @@
|
|||||||
/* eslint-disable react/jsx-props-no-spreading */
|
import { Col, Input, Row } from 'antd';
|
||||||
import { Col, Row } from 'antd';
|
// ** Constants
|
||||||
|
import {
|
||||||
|
initialAggregateAttribute,
|
||||||
|
mapOfFilters,
|
||||||
|
mapOfOperators,
|
||||||
|
} from 'constants/queryBuilder';
|
||||||
|
import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
|
||||||
// ** Components
|
// ** Components
|
||||||
import {
|
import {
|
||||||
|
AdditionalFiltersToggler,
|
||||||
DataSourceDropdown,
|
DataSourceDropdown,
|
||||||
FilterLabel,
|
FilterLabel,
|
||||||
ListMarker,
|
ListMarker,
|
||||||
@ -10,64 +17,178 @@ import {
|
|||||||
AggregatorFilter,
|
AggregatorFilter,
|
||||||
GroupByFilter,
|
GroupByFilter,
|
||||||
OperatorsSelect,
|
OperatorsSelect,
|
||||||
|
ReduceToFilter,
|
||||||
} from 'container/QueryBuilder/filters';
|
} from 'container/QueryBuilder/filters';
|
||||||
// Context
|
// Context
|
||||||
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/useQueryBuilder';
|
||||||
|
import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
|
||||||
// ** Hooks
|
// ** Hooks
|
||||||
import React 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 { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
// ** Constants
|
|
||||||
import {
|
|
||||||
LogsAggregatorOperator,
|
|
||||||
MetricAggregateOperator,
|
|
||||||
TracesAggregatorOperator,
|
|
||||||
} 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
|
||||||
|
import { StyledDeleteEntity, StyledRow } from './Query.styled';
|
||||||
|
|
||||||
const mapOfOperators: Record<DataSource, string[]> = {
|
export const Query = memo(function Query({
|
||||||
metrics: Object.values(MetricAggregateOperator),
|
|
||||||
logs: Object.values(LogsAggregatorOperator),
|
|
||||||
traces: Object.values(TracesAggregatorOperator),
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Query({
|
|
||||||
index,
|
index,
|
||||||
isAvailableToDisable,
|
isAvailableToDisable,
|
||||||
queryVariant,
|
queryVariant,
|
||||||
query,
|
query,
|
||||||
|
panelType,
|
||||||
}: QueryProps): JSX.Element {
|
}: QueryProps): JSX.Element {
|
||||||
const { handleSetQueryData } = useQueryBuilder();
|
const {
|
||||||
|
handleSetQueryData,
|
||||||
|
removeEntityByIndex,
|
||||||
|
initialDataSource,
|
||||||
|
} = useQueryBuilder();
|
||||||
|
|
||||||
const currentListOfOperators = mapOfOperators[query.dataSource];
|
const currentListOfOperators = useMemo(
|
||||||
|
() => mapOfOperators[query.dataSource],
|
||||||
|
[query],
|
||||||
|
);
|
||||||
|
const listOfAdditionalFilters = useMemo(() => mapOfFilters[query.dataSource], [
|
||||||
|
query,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleChangeOperator = (value: string): void => {
|
const handleChangeOperator = useCallback(
|
||||||
handleSetQueryData(index, { aggregateOperator: value });
|
(value: string): void => {
|
||||||
};
|
const aggregateDataType: BaseAutocompleteData['dataType'] =
|
||||||
|
query.aggregateAttribute.dataType;
|
||||||
|
|
||||||
const handleChangeDataSource = (nextSource: DataSource): void => {
|
const newQuery: IBuilderQueryForm = {
|
||||||
handleSetQueryData(index, { dataSource: nextSource });
|
...query,
|
||||||
};
|
aggregateOperator: value,
|
||||||
|
having: [],
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleDisableQuery = (): void => {
|
if (!aggregateDataType || query.dataSource === DataSource.METRICS) {
|
||||||
handleSetQueryData(index, { disabled: !query.disabled });
|
handleSetQueryData(index, newQuery);
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleChangeAggregatorAttribute = (
|
switch (aggregateDataType) {
|
||||||
value: BaseAutocompleteData,
|
case 'string':
|
||||||
): void => {
|
case 'bool': {
|
||||||
handleSetQueryData(index, { aggregateAttribute: value });
|
const typeOfValue = findDataTypeOfOperator(value);
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeGroupByKeys = (values: BaseAutocompleteData[]): void => {
|
handleSetQueryData(index, {
|
||||||
handleSetQueryData(index, { groupBy: values });
|
...newQuery,
|
||||||
};
|
...(typeOfValue === 'number'
|
||||||
|
? { aggregateAttribute: initialAggregateAttribute }
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'float64':
|
||||||
|
case 'int64': {
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[index, query, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeAggregatorAttribute = useCallback(
|
||||||
|
(value: BaseAutocompleteData): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
aggregateAttribute: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[index, query, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeDataSource = useCallback(
|
||||||
|
(nextSource: DataSource): void => {
|
||||||
|
let newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
dataSource: nextSource,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nextSource !== query.dataSource) {
|
||||||
|
const initCopy = {
|
||||||
|
...(initialQueryBuilderFormValues as Partial<IBuilderQueryForm>),
|
||||||
|
};
|
||||||
|
delete initCopy.queryName;
|
||||||
|
|
||||||
|
newQuery = {
|
||||||
|
...newQuery,
|
||||||
|
...initCopy,
|
||||||
|
dataSource: initialDataSource || nextSource,
|
||||||
|
aggregateOperator: mapOfOperators[nextSource][0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[index, query, initialDataSource, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggleDisableQuery = useCallback((): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
disabled: !query.disabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
}, [index, query, handleSetQueryData]);
|
||||||
|
|
||||||
|
const handleChangeGroupByKeys = useCallback(
|
||||||
|
(values: BaseAutocompleteData[]): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
groupBy: values,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[index, query, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeQueryLegend = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
legend: e.target.value,
|
||||||
|
};
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[index, query, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeReduceTo = useCallback(
|
||||||
|
(value: string): void => {
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...query,
|
||||||
|
reduceTo: value,
|
||||||
|
};
|
||||||
|
handleSetQueryData(index, newQuery);
|
||||||
|
},
|
||||||
|
[index, query, handleSetQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteQuery = useCallback(() => {
|
||||||
|
removeEntityByIndex('queryData', index);
|
||||||
|
}, [removeEntityByIndex, index]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[0, 15]}>
|
<StyledRow gutter={[0, 15]}>
|
||||||
|
<StyledDeleteEntity onClick={handleDeleteQuery} />
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Row wrap={false} align="middle">
|
<Row wrap={false} align="middle">
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
@ -92,14 +213,14 @@ export function Query({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={11}>
|
<Col span={11}>
|
||||||
<Row gutter={[11, 5]}>
|
<Row gutter={[11, 5]}>
|
||||||
<Col flex="95px">
|
<Col flex="5.93rem">
|
||||||
<OperatorsSelect
|
<OperatorsSelect
|
||||||
value={query.aggregateOperator || currentListOfOperators[0]}
|
value={query.aggregateOperator || currentListOfOperators[0]}
|
||||||
onChange={handleChangeOperator}
|
onChange={handleChangeOperator}
|
||||||
operators={currentListOfOperators}
|
operators={currentListOfOperators}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col flex="1 1 200px">
|
<Col flex="1 1 12.5rem">
|
||||||
<AggregatorFilter
|
<AggregatorFilter
|
||||||
onChange={handleChangeAggregatorAttribute}
|
onChange={handleChangeAggregatorAttribute}
|
||||||
query={query}
|
query={query}
|
||||||
@ -109,14 +230,32 @@ export function Query({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={11} offset={2}>
|
<Col span={11} offset={2}>
|
||||||
<Row gutter={[11, 5]}>
|
<Row gutter={[11, 5]}>
|
||||||
<Col flex="95px">
|
<Col flex="5.93rem">
|
||||||
<FilterLabel label="Group by" />
|
<FilterLabel label={panelType === 'VALUE' ? 'Reduce to' : 'Group by'} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col flex="1 1 200px">
|
<Col flex="1 1 12.5rem">
|
||||||
<GroupByFilter query={query} onChange={handleChangeGroupByKeys} />
|
{panelType === 'VALUE' ? (
|
||||||
|
<ReduceToFilter query={query} onChange={handleChangeReduceTo} />
|
||||||
|
) : (
|
||||||
|
<GroupByFilter query={query} onChange={handleChangeGroupByKeys} />
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
<Col span={24}>
|
||||||
|
<AdditionalFiltersToggler listOfAdditionalFilter={listOfAdditionalFilters}>
|
||||||
|
{/* TODO: Render filter by Col component */}
|
||||||
|
test additional filter
|
||||||
|
</AdditionalFiltersToggler>
|
||||||
|
</Col>
|
||||||
|
<Row style={{ width: '100%' }}>
|
||||||
|
<Input
|
||||||
|
onChange={handleChangeQueryLegend}
|
||||||
|
size="middle"
|
||||||
|
value={query.legend}
|
||||||
|
addonBefore="Legend Format"
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</StyledRow>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
export { AdditionalFiltersToggler } from './AdditionalFiltersToggler';
|
||||||
export { DataSourceDropdown } from './DataSourceDropdown';
|
export { DataSourceDropdown } from './DataSourceDropdown';
|
||||||
export { FilterLabel } from './FilterLabel';
|
export { FilterLabel } from './FilterLabel';
|
||||||
export { Formula } from './Formula';
|
export { Formula } from './Formula';
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
import { AutoComplete, Spin } from 'antd';
|
import { AutoComplete, Spin } from 'antd';
|
||||||
// ** Api
|
// ** Api
|
||||||
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
|
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
|
||||||
|
import { initialAggregateAttribute } from 'constants/queryBuilder';
|
||||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
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 { useQuery } from 'react-query';
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
import { transformToUpperCase } from 'utils/transformToUpperCase';
|
||||||
@ -11,7 +12,7 @@ import { transformToUpperCase } from 'utils/transformToUpperCase';
|
|||||||
// ** Types
|
// ** Types
|
||||||
import { AgregatorFilterProps } from './AggregatorFilter.intefaces';
|
import { AgregatorFilterProps } from './AggregatorFilter.intefaces';
|
||||||
|
|
||||||
export function AggregatorFilter({
|
export const AggregatorFilter = memo(function AggregatorFilter({
|
||||||
onChange,
|
onChange,
|
||||||
query,
|
query,
|
||||||
}: AgregatorFilterProps): JSX.Element {
|
}: AgregatorFilterProps): JSX.Element {
|
||||||
@ -50,7 +51,7 @@ export function AggregatorFilter({
|
|||||||
const handleChangeAttribute = (value: string): void => {
|
const handleChangeAttribute = (value: string): void => {
|
||||||
const currentAttributeObj = data?.payload?.attributeKeys?.find(
|
const currentAttributeObj = data?.payload?.attributeKeys?.find(
|
||||||
(item) => item.key === value,
|
(item) => item.key === value,
|
||||||
) || { key: value, type: null, dataType: null, isColumn: null };
|
) || { ...initialAggregateAttribute, key: value };
|
||||||
|
|
||||||
onChange(currentAttributeObj);
|
onChange(currentAttributeObj);
|
||||||
};
|
};
|
||||||
@ -79,4 +80,4 @@ export function AggregatorFilter({
|
|||||||
onChange={handleChangeAttribute}
|
onChange={handleChangeAttribute}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -2,11 +2,11 @@ import { Select, Spin } from 'antd';
|
|||||||
// ** Api
|
// ** Api
|
||||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||||
// ** Constants
|
// ** Constants
|
||||||
import { QueryBuilderKeys } from 'constants/useQueryKeys';
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
// ** Components
|
// ** Components
|
||||||
// ** Helpers
|
// ** Helpers
|
||||||
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
|
||||||
import React, { useState } from 'react';
|
import React, { memo, 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 { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
GroupByFilterValue,
|
GroupByFilterValue,
|
||||||
} from './GroupByFilter.interfaces';
|
} from './GroupByFilter.interfaces';
|
||||||
|
|
||||||
export function GroupByFilter({
|
export const GroupByFilter = memo(function GroupByFilter({
|
||||||
query,
|
query,
|
||||||
onChange,
|
onChange,
|
||||||
}: GroupByFilterProps): JSX.Element {
|
}: GroupByFilterProps): JSX.Element {
|
||||||
@ -97,4 +97,4 @@ export function GroupByFilter({
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
// ** Types
|
// ** Types
|
||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
// ** Helpers
|
// ** Helpers
|
||||||
@ -7,7 +7,7 @@ import { transformToUpperCase } from 'utils/transformToUpperCase';
|
|||||||
|
|
||||||
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
|
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
|
||||||
|
|
||||||
export function OperatorsSelect({
|
export const OperatorsSelect = memo(function OperatorsSelect({
|
||||||
operators,
|
operators,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
@ -30,4 +30,4 @@ export function OperatorsSelect({
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import { SelectProps } from 'antd';
|
||||||
|
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export type ReduceToFilterProps = Omit<SelectProps, 'onChange' | 'value'> & {
|
||||||
|
query: IBuilderQueryForm;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
};
|
@ -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<string, string>[] = Object.values(
|
||||||
|
EReduceOperator,
|
||||||
|
).map((str) => ({ label: str, value: str }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
placeholder="Reduce to"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={options}
|
||||||
|
value={query.reduceTo}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1 @@
|
|||||||
|
export { ReduceToFilter } from './ReduceToFilter';
|
@ -1,3 +1,4 @@
|
|||||||
export { AggregatorFilter } from './AggregatorFilter';
|
export { AggregatorFilter } from './AggregatorFilter';
|
||||||
export { GroupByFilter } from './GroupByFilter';
|
export { GroupByFilter } from './GroupByFilter';
|
||||||
export { OperatorsSelect } from './OperatorsSelect';
|
export { OperatorsSelect } from './OperatorsSelect';
|
||||||
|
export { ReduceToFilter } from './ReduceToFilter';
|
||||||
|
9
frontend/src/lib/newQueryBuilder/createNewFormulaName.ts
Normal file
9
frontend/src/lib/newQueryBuilder/createNewFormulaName.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const MAX_FORMULAS = 20;
|
||||||
|
|
||||||
|
const currentArray: string[] = Array.from(
|
||||||
|
Array(MAX_FORMULAS),
|
||||||
|
(_, i) => `F${i + 1}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createNewFormulaName = (index: number): string =>
|
||||||
|
currentArray[index];
|
14
frontend/src/lib/newQueryBuilder/createNewQueryName.ts
Normal file
14
frontend/src/lib/newQueryBuilder/createNewQueryName.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const MAX_QUERIES = 26;
|
||||||
|
|
||||||
|
const alpha: number[] = Array.from(Array(MAX_QUERIES), (_, i) => i + 65);
|
||||||
|
const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
|
||||||
|
|
||||||
|
export const createNewQueryName = (existNames: string[]): string => {
|
||||||
|
for (let i = 0; i < alphabet.length; i += 1) {
|
||||||
|
if (!existNames.includes(alphabet[i])) {
|
||||||
|
return alphabet[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
22
frontend/src/lib/query/findDataTypeOfOperator.ts
Normal file
22
frontend/src/lib/query/findDataTypeOfOperator.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { operatorsByTypes } from 'constants/queryBuilder';
|
||||||
|
import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const findDataTypeOfOperator = (value: string): LocalDataType | null => {
|
||||||
|
const entries = Object.entries(operatorsByTypes) as [
|
||||||
|
LocalDataType,
|
||||||
|
string[],
|
||||||
|
][];
|
||||||
|
|
||||||
|
for (let i = 0; i < entries.length; i += 1) {
|
||||||
|
for (let j = 0; j < entries[i][1].length; j += 1) {
|
||||||
|
const currentOperator = entries[i][1][j];
|
||||||
|
const type = entries[i][0];
|
||||||
|
|
||||||
|
if (currentOperator === value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
@ -1,4 +1,11 @@
|
|||||||
// ** Helpers
|
// ** Helpers
|
||||||
|
// ** Constants
|
||||||
|
import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
|
||||||
|
import { mapOfOperators } from 'constants/queryBuilder';
|
||||||
|
import {
|
||||||
|
createNewQueryName,
|
||||||
|
MAX_QUERIES,
|
||||||
|
} from 'lib/newQueryBuilder/createNewQueryName';
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
@ -14,17 +21,20 @@ import {
|
|||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import {
|
import {
|
||||||
DataSource,
|
DataSource,
|
||||||
MetricAggregateOperator,
|
|
||||||
QueryBuilderContextType,
|
QueryBuilderContextType,
|
||||||
QueryBuilderData,
|
QueryBuilderData,
|
||||||
} from 'types/common/queryBuilder';
|
} from 'types/common/queryBuilder';
|
||||||
|
|
||||||
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
|
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
|
||||||
queryBuilderData: { queryData: [], queryFormulas: [] },
|
queryBuilderData: { queryData: [], queryFormulas: [] },
|
||||||
|
initialDataSource: null,
|
||||||
resetQueryBuilderData: () => {},
|
resetQueryBuilderData: () => {},
|
||||||
handleSetQueryData: () => {},
|
handleSetQueryData: () => {},
|
||||||
handleSetFormulaData: () => {},
|
handleSetFormulaData: () => {},
|
||||||
initQueryBuilderData: () => {},
|
initQueryBuilderData: () => {},
|
||||||
|
setupInitialDataSource: () => {},
|
||||||
|
removeEntityByIndex: () => {},
|
||||||
|
addNewQuery: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialQueryBuilderData: QueryBuilderData = {
|
const initialQueryBuilderData: QueryBuilderData = {
|
||||||
@ -35,27 +45,15 @@ const initialQueryBuilderData: QueryBuilderData = {
|
|||||||
export function QueryBuilderProvider({
|
export function QueryBuilderProvider({
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren): JSX.Element {
|
}: PropsWithChildren): JSX.Element {
|
||||||
// ** TODO: get queryId from url for getting data for query builder
|
// TODO: this is temporary. It will be used when we have fixed dataSource and need create new query with this data source
|
||||||
// ** TODO: type the params which will be used for request of the data for query builder
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const [initialDataSource, setInitialDataSource] = useState<DataSource | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: when initialDataSource will be setuped, on create button initial dataSource will from initialDataSource
|
||||||
const [queryBuilderData, setQueryBuilderData] = useState<QueryBuilderData>({
|
const [queryBuilderData, setQueryBuilderData] = useState<QueryBuilderData>({
|
||||||
// ** TODO temporary initial value for first query for testing first filters
|
queryData: [],
|
||||||
queryData: [
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
{
|
|
||||||
dataSource: DataSource.METRICS,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: Object.values(MetricAggregateOperator)[0],
|
|
||||||
aggregateAttribute: {
|
|
||||||
dataType: null,
|
|
||||||
key: '',
|
|
||||||
isColumn: null,
|
|
||||||
type: null,
|
|
||||||
},
|
|
||||||
groupBy: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
queryFormulas: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,7 +62,8 @@ export function QueryBuilderProvider({
|
|||||||
setQueryBuilderData(initialQueryBuilderData);
|
setQueryBuilderData(initialQueryBuilderData);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ** Method for setupping query builder data
|
// ** Method for setuping query builder data
|
||||||
|
// ** Before setuping transform data from backend to frontend format
|
||||||
const initQueryBuilderData = useCallback(
|
const initQueryBuilderData = useCallback(
|
||||||
(queryBuilderData: QueryBuilderData): void => {
|
(queryBuilderData: QueryBuilderData): void => {
|
||||||
setQueryBuilderData(queryBuilderData);
|
setQueryBuilderData(queryBuilderData);
|
||||||
@ -72,44 +71,112 @@ export function QueryBuilderProvider({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSetQueryData = useCallback(
|
const removeEntityByIndex = useCallback(
|
||||||
(index: number, newQueryData: Partial<IBuilderQueryForm>): void => {
|
(type: keyof QueryBuilderData, index: number) => {
|
||||||
const updatedQueryBuilderData = queryBuilderData.queryData.map((item, idx) =>
|
setQueryBuilderData((prevState) => {
|
||||||
index === idx ? { ...item, ...newQueryData } : item,
|
const currentArray: (IBuilderQueryForm | IBuilderFormula)[] =
|
||||||
);
|
prevState[type];
|
||||||
|
return {
|
||||||
setQueryBuilderData((prevState) => ({
|
...prevState,
|
||||||
...prevState,
|
[type]: currentArray.filter((item, i) => index !== i),
|
||||||
queryData: updatedQueryBuilderData,
|
};
|
||||||
}));
|
});
|
||||||
},
|
},
|
||||||
[queryBuilderData],
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const createNewQuery = useCallback(
|
||||||
|
(queries: IBuilderQueryForm[]): IBuilderQueryForm => {
|
||||||
|
const existNames = queries.map((item) => item.queryName);
|
||||||
|
|
||||||
|
const newQuery: IBuilderQueryForm = {
|
||||||
|
...initialQueryBuilderFormValues,
|
||||||
|
queryName: createNewQueryName(existNames),
|
||||||
|
...(initialDataSource
|
||||||
|
? {
|
||||||
|
dataSource: initialDataSource,
|
||||||
|
aggregateOperator: mapOfOperators[initialDataSource][0],
|
||||||
|
expression: createNewQueryName(existNames),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return newQuery;
|
||||||
|
},
|
||||||
|
[initialDataSource],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addNewQuery = useCallback(() => {
|
||||||
|
setQueryBuilderData((prevState) => {
|
||||||
|
if (prevState.queryData.length >= MAX_QUERIES) return prevState;
|
||||||
|
|
||||||
|
const newQuery = createNewQuery(prevState.queryData);
|
||||||
|
|
||||||
|
return { ...prevState, queryData: [...prevState.queryData, newQuery] };
|
||||||
|
});
|
||||||
|
}, [createNewQuery]);
|
||||||
|
|
||||||
|
const setupInitialDataSource = useCallback(
|
||||||
|
(newInitialDataSource: DataSource | null) =>
|
||||||
|
setInitialDataSource(newInitialDataSource),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateQueryBuilderData = useCallback(
|
||||||
|
(
|
||||||
|
queries: IBuilderQueryForm[],
|
||||||
|
index: number,
|
||||||
|
newQueryData: IBuilderQueryForm,
|
||||||
|
) => queries.map((item, idx) => (index === idx ? newQueryData : item)),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetQueryData = useCallback(
|
||||||
|
(index: number, newQueryData: IBuilderQueryForm): void => {
|
||||||
|
setQueryBuilderData((prevState) => {
|
||||||
|
const updatedQueryBuilderData = updateQueryBuilderData(
|
||||||
|
prevState.queryData,
|
||||||
|
index,
|
||||||
|
newQueryData,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
queryData: updatedQueryBuilderData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[updateQueryBuilderData],
|
||||||
);
|
);
|
||||||
const handleSetFormulaData = useCallback(
|
const handleSetFormulaData = useCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
(index: number, formulaData: IBuilderFormula): void => {},
|
(index: number, formulaData: IBuilderFormula): void => {},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
console.log(queryBuilderData.queryData);
|
||||||
// ** TODO: Discuss with Palash how the state of the queryBuilder and queryFormulas
|
|
||||||
// ** TODO: should be filled from url
|
|
||||||
|
|
||||||
// ** TODO: put these values and setter to the context value
|
|
||||||
|
|
||||||
const contextValues: QueryBuilderContextType = useMemo(
|
const contextValues: QueryBuilderContextType = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
queryBuilderData,
|
queryBuilderData,
|
||||||
|
initialDataSource,
|
||||||
resetQueryBuilderData,
|
resetQueryBuilderData,
|
||||||
handleSetQueryData,
|
handleSetQueryData,
|
||||||
handleSetFormulaData,
|
handleSetFormulaData,
|
||||||
initQueryBuilderData,
|
initQueryBuilderData,
|
||||||
|
setupInitialDataSource,
|
||||||
|
removeEntityByIndex,
|
||||||
|
addNewQuery,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
queryBuilderData,
|
queryBuilderData,
|
||||||
|
initialDataSource,
|
||||||
resetQueryBuilderData,
|
resetQueryBuilderData,
|
||||||
handleSetQueryData,
|
handleSetQueryData,
|
||||||
handleSetFormulaData,
|
handleSetFormulaData,
|
||||||
initQueryBuilderData,
|
initQueryBuilderData,
|
||||||
|
setupInitialDataSource,
|
||||||
|
removeEntityByIndex,
|
||||||
|
addNewQuery,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
export type LocalDataType = 'number' | 'string' | 'bool';
|
||||||
|
|
||||||
|
export type DataType = 'int64' | 'float64' | 'string' | 'bool';
|
||||||
|
|
||||||
export interface BaseAutocompleteData {
|
export interface BaseAutocompleteData {
|
||||||
dataType: 'number' | 'string' | 'boolean' | null;
|
dataType: DataType | null;
|
||||||
isColumn: boolean | null;
|
isColumn: boolean | null;
|
||||||
key: string;
|
key: string;
|
||||||
type: 'tag' | 'resource' | null;
|
type: 'tag' | 'resource' | null;
|
||||||
|
@ -23,6 +23,12 @@ export interface TagFilter {
|
|||||||
op: string;
|
op: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Having {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
op: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Type for query builder
|
// Type for query builder
|
||||||
export type IBuilderQuery = {
|
export type IBuilderQuery = {
|
||||||
queryName: string;
|
queryName: string;
|
||||||
@ -33,12 +39,14 @@ export type IBuilderQuery = {
|
|||||||
groupBy: BaseAutocompleteData[];
|
groupBy: BaseAutocompleteData[];
|
||||||
expression: string;
|
expression: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
having?: string;
|
having: Having[];
|
||||||
limit?: number;
|
limit: number;
|
||||||
orderBy?: string[];
|
stepInterval: number;
|
||||||
reduceTo?: string;
|
orderBy: string[];
|
||||||
|
reduceTo: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IBuilderQueryForm = Omit<IBuilderQuery, 'aggregateAttribute'> & {
|
export type IBuilderQueryForm = Omit<IBuilderQuery, 'aggregateAttribute'> & {
|
||||||
aggregateAttribute: BaseAutocompleteData;
|
aggregateAttribute: BaseAutocompleteData;
|
||||||
|
legend: string;
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,49 @@ export enum DataSource {
|
|||||||
LOGS = 'logs',
|
LOGS = 'logs',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum StringOperators {
|
||||||
|
NOOP = 'noop',
|
||||||
|
COUNT = 'count',
|
||||||
|
COUNT_DISTINCT = 'count_distinct',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NumberOperators {
|
||||||
|
SUM = 'sum',
|
||||||
|
AVG = 'avg',
|
||||||
|
MAX = 'max',
|
||||||
|
MIN = 'min',
|
||||||
|
P05 = 'p05',
|
||||||
|
P10 = 'p10',
|
||||||
|
P20 = 'p20',
|
||||||
|
P25 = 'p25',
|
||||||
|
P50 = 'p50',
|
||||||
|
P75 = 'p75',
|
||||||
|
P90 = 'p90',
|
||||||
|
P95 = 'p95',
|
||||||
|
P99 = 'p99',
|
||||||
|
RATE = 'rate',
|
||||||
|
SUM_RATE = 'sum_rate',
|
||||||
|
AVG_RATE = 'avg_rate',
|
||||||
|
MAX_RATE = 'max_rate',
|
||||||
|
MIN_RATE = 'min_rate',
|
||||||
|
RATE_SUM = 'rate_sum',
|
||||||
|
RATE_AVG = 'rate_avg',
|
||||||
|
RATE_MIN = 'rate_min',
|
||||||
|
RATE_MAX = 'rate_max',
|
||||||
|
HIST_QUANTILE_50 = 'hist_quantile_50',
|
||||||
|
HIST_QUANTILE_75 = 'hist_quantile_75',
|
||||||
|
HIST_QUANTILE_90 = 'hist_quantile_90',
|
||||||
|
HIST_QUANTILE_95 = 'hist_quantile_95',
|
||||||
|
HIST_QUANTILE_99 = 'hist_quantile_99',
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add boolean operators from backend
|
||||||
|
export enum BoolOperators {
|
||||||
|
NOOP = 'noop',
|
||||||
|
COUNT = 'count',
|
||||||
|
COUNT_DISTINCT = 'count_distinct',
|
||||||
|
}
|
||||||
|
|
||||||
export enum MetricAggregateOperator {
|
export enum MetricAggregateOperator {
|
||||||
NOOP = 'noop',
|
NOOP = 'noop',
|
||||||
COUNT = 'count',
|
COUNT = 'count',
|
||||||
@ -82,6 +125,14 @@ export enum LogsAggregatorOperator {
|
|||||||
RATE = 'rate',
|
RATE = 'rate',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EReduceOperator {
|
||||||
|
LATEST_OF_VALUES_IN_TIMEFRAME = 'Latest of values in timeframe',
|
||||||
|
'SUM_OF_VALUES_IN_TIMEFRAME' = 'Sum of values in timeframe',
|
||||||
|
'AVERAGE_OF_VALUES_IN_TIMEFRAME' = 'Average of values in timeframe',
|
||||||
|
'MAX_OF_VALUES_IN_TIMEFRAME' = 'Max of values in timeframe',
|
||||||
|
'MIN_OF_VALUES_IN_TIMEFRAME' = 'Min of values in timeframe',
|
||||||
|
}
|
||||||
|
|
||||||
export type QueryBuilderData = {
|
export type QueryBuilderData = {
|
||||||
queryData: IBuilderQueryForm[];
|
queryData: IBuilderQueryForm[];
|
||||||
queryFormulas: IBuilderFormula[];
|
queryFormulas: IBuilderFormula[];
|
||||||
@ -90,11 +141,12 @@ export type QueryBuilderData = {
|
|||||||
// ** TODO: temporary types for context, fix it during development
|
// ** TODO: temporary types for context, fix it during development
|
||||||
export type QueryBuilderContextType = {
|
export type QueryBuilderContextType = {
|
||||||
queryBuilderData: QueryBuilderData;
|
queryBuilderData: QueryBuilderData;
|
||||||
|
initialDataSource: DataSource | null;
|
||||||
resetQueryBuilderData: () => void;
|
resetQueryBuilderData: () => void;
|
||||||
handleSetQueryData: (
|
handleSetQueryData: (index: number, queryData: IBuilderQueryForm) => void;
|
||||||
index: number,
|
|
||||||
queryData: Partial<IBuilderQueryForm>,
|
|
||||||
) => void;
|
|
||||||
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
|
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
|
||||||
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
|
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
|
||||||
|
setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
|
||||||
|
removeEntityByIndex: (type: keyof QueryBuilderData, index: number) => void;
|
||||||
|
addNewQuery: () => void;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user