refactor(query_builder): remove old codebase (#2686)

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-05-12 10:51:59 +03:00 committed by GitHub
parent 76331001b7
commit 9da399023b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 13 additions and 1118 deletions

View File

@ -1,5 +1,3 @@
import { EAggregateOperator, EReduceOperator } from 'types/common/dashboard';
export const PromQLQueryTemplate = {
query: '',
legend: '',
@ -11,24 +9,3 @@ export const ClickHouseQueryTemplate = {
legend: '',
disabled: false,
};
export const QueryBuilderQueryTemplate = {
metricName: null,
aggregateOperator: EAggregateOperator.NOOP,
tagFilters: {
op: 'AND',
items: [],
},
legend: '',
disabled: false,
// Specific to TIME_SERIES type graph
groupBy: [],
// Specific to VALUE type graph
reduceTo: EReduceOperator['Latest of values in timeframe'],
};
export const QueryBuilderFormulaTemplate = {
expression: '',
disabled: false,
legend: '',
};

View File

@ -1,18 +0,0 @@
import { EAggregateOperator } from 'types/common/dashboard';
export const AggregateFunctions = Object.keys(EAggregateOperator)
.filter((key) => Number.isNaN(parseInt(key, 10)))
.map((key) => ({
label: key,
value: EAggregateOperator[key as keyof typeof EAggregateOperator],
}));
export const TagKeyOperator = [
{ label: 'In', value: 'IN' },
{ label: 'Not In', value: 'NIN' },
{ label: 'Like', value: 'LIKE' },
{ label: 'Not Like', value: 'NLIKE' },
// { label: 'Equal', value: 'EQ' },
// { label: 'Not Equal', value: 'NEQ' },
// { label: 'REGEX', value: 'REGEX' },
];

View File

@ -3,8 +3,8 @@ import { ClickHouseQueryTemplate } from 'constants/dashboard';
import GetQueryName from 'lib/query/GetQueryName';
import React from 'react';
import { Query } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import { WIDGET_CLICKHOUSE_QUERY_KEY_NAME } from '../../constants';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import ClickHouseQueryBuilder from './query';
@ -35,7 +35,7 @@ function ClickHouseQueryContainer({
// hence, this method is only applies when queryIndex is in number format.
if (typeof queryIndex === 'number') {
const allQueries = queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME];
const allQueries = queryData[EQueryType.CLICKHOUSE];
const currentIndexQuery = allQueries[queryIndex];
@ -57,8 +57,8 @@ function ClickHouseQueryContainer({
}
};
const addQueryHandler = (): void => {
queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME].push({
name: GetQueryName(queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME]) || '',
queryData[EQueryType.CLICKHOUSE].push({
name: GetQueryName(queryData[EQueryType.CLICKHOUSE]) || '',
...ClickHouseQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });

View File

@ -3,8 +3,8 @@ import { PromQLQueryTemplate } from 'constants/dashboard';
import GetQueryName from 'lib/query/GetQueryName';
import React from 'react';
import { IPromQLQuery, Query } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import { WIDGET_PROMQL_QUERY_KEY_NAME } from '../../constants';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import PromQLQueryBuilder from './query';
@ -28,7 +28,7 @@ function PromQLQueryContainer({
toggleDisable,
toggleDelete,
}: IPromQLQueryHandleChange): void => {
const allQueries = queryData[WIDGET_PROMQL_QUERY_KEY_NAME];
const allQueries = queryData[EQueryType.PROM];
const currentIndexQuery = allQueries[queryIndex as number];
if (query !== undefined) currentIndexQuery.query = query;
if (legend !== undefined) currentIndexQuery.legend = legend;
@ -42,8 +42,8 @@ function PromQLQueryContainer({
updateQueryData({ updatedQuery: { ...queryData } });
};
const addQueryHandler = (): void => {
queryData[WIDGET_PROMQL_QUERY_KEY_NAME].push({
name: GetQueryName(queryData[WIDGET_PROMQL_QUERY_KEY_NAME]) || '',
queryData[EQueryType.PROM].push({
name: GetQueryName(queryData[EQueryType.PROM]) || '',
...PromQLQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });

View File

@ -1,61 +0,0 @@
import { createMachine } from 'xstate';
export const ResourceAttributesFilterMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */
createMachine({
tsTypes: {} as import('./MetricTagKey.machine.typegen').Typegen0,
initial: 'Idle',
states: {
TagKey: {
on: {
NEXT: {
actions: 'onSelectOperator',
target: 'Operator',
},
onBlur: {
actions: 'onBlurPurge',
target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Operator: {
on: {
NEXT: {
actions: 'onSelectTagValue',
target: 'TagValue',
},
// onBlur: {
// actions: 'onBlurPurge',
// target: 'Idle',
// },
RESET: {
target: 'Idle',
},
},
},
TagValue: {
on: {
onBlur: {
actions: ['onValidateQuery'],
// target: 'Idle',
},
RESET: {
target: 'Idle',
},
},
},
Idle: {
on: {
NEXT: {
actions: 'onSelectTagKey',
description: 'Select Category',
target: 'TagKey',
},
},
},
},
id: 'Dashboard Search And Filter',
});

View File

@ -1,32 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'xstate.init': { type: 'xstate.init' };
};
invokeSrcNameMap: {};
missingImplementations: {
actions:
| 'onBlurPurge'
| 'onSelectOperator'
| 'onSelectTagKey'
| 'onSelectTagValue'
| 'onValidateQuery';
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
onBlurPurge: 'onBlur';
onSelectOperator: 'NEXT';
onSelectTagKey: 'NEXT';
onSelectTagValue: 'NEXT';
onValidateQuery: 'onBlur';
};
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {};
matchesStates: 'Idle' | 'Operator' | 'TagKey' | 'TagValue';
tags: never;
}

View File

@ -1,34 +0,0 @@
import React from 'react';
import { QueryChipContainer, QueryChipItem } from './styles';
import { ITagKeyValueQuery } from './types';
interface IQueryChipProps {
queryData: ITagKeyValueQuery;
onClose: (id: string) => void;
disabled?: boolean;
}
export default function QueryChip({
queryData,
onClose,
disabled,
}: IQueryChipProps): JSX.Element {
return (
<QueryChipContainer>
<QueryChipItem>{queryData.key}</QueryChipItem>
<QueryChipItem>{queryData.op}</QueryChipItem>
<QueryChipItem
closable={!disabled}
onClose={(): void => {
if (!disabled) onClose(queryData.id);
}}
>
{queryData.value.join(', ')}
</QueryChipItem>
</QueryChipContainer>
);
}
QueryChip.defaultProps = {
disabled: false,
};

View File

@ -1,209 +0,0 @@
import { CloseCircleFilled } from '@ant-design/icons';
import { useMachine } from '@xstate/react';
import { Button, Select, Spin } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { map } from 'lodash-es';
import React, { useCallback, useEffect, useState } from 'react';
import { IMetricsBuilderQuery } from 'types/api/dashboard/getAll';
import { v4 as uuid } from 'uuid';
import { ResourceAttributesFilterMachine } from './MetricTagKey.machine';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { IOption, ITagKeyValueQuery } from './types';
import {
createQuery,
GetTagKeys,
GetTagValues,
OperatorSchema,
SingleValueOperators,
} from './utils';
interface IMetricTagKeyFilterProps {
metricName: IMetricsBuilderQuery['metricName'];
onSetQuery: (args: IMetricsBuilderQuery['tagFilters']['items']) => void;
selectedTagFilters: IMetricsBuilderQuery['tagFilters']['items'];
}
function MetricTagKeyFilter({
metricName,
onSetQuery,
selectedTagFilters: selectedTagQueries,
}: IMetricTagKeyFilterProps): JSX.Element | null {
const isDarkMode = useIsDarkMode();
const [loading, setLoading] = useState(true);
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [staging, setStaging] = useState<string[]>([]);
const [queries, setQueries] = useState<ITagKeyValueQuery[]>([]);
const [optionsData, setOptionsData] = useState<{
mode: undefined | 'tags' | 'multiple';
options: IOption[];
}>({
mode: undefined,
options: [],
});
const dispatchQueries = (
updatedQueries: IMetricsBuilderQuery['tagFilters']['items'],
): void => {
onSetQuery(updatedQueries);
setQueries(updatedQueries);
};
const handleLoading = (isLoading: boolean): void => {
setLoading(isLoading);
if (isLoading) {
setOptionsData({ mode: undefined, options: [] });
}
};
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
actions: {
onSelectTagKey: () => {
handleLoading(true);
GetTagKeys(metricName || '')
.then((tagKeys) => setOptionsData({ options: tagKeys, mode: undefined }))
.finally(() => {
handleLoading(false);
});
},
onSelectOperator: () => {
setOptionsData({ options: OperatorSchema, mode: undefined });
},
onSelectTagValue: () => {
handleLoading(true);
GetTagValues(staging[0], metricName || '')
.then((tagValuesOptions) =>
setOptionsData({ options: tagValuesOptions, mode: 'tags' }),
)
.finally(() => {
handleLoading(false);
});
},
onBlurPurge: () => {
setSelectedValues([]);
setStaging([]);
},
onValidateQuery: (): void => {
if (staging.length < 2 || selectedValues.length === 0) {
return;
}
const generatedQuery = createQuery([...staging, selectedValues]);
if (generatedQuery) {
dispatchQueries([...queries, generatedQuery]);
setSelectedValues([]);
setStaging([]);
send('RESET');
}
},
},
});
useEffect(() => {
setQueries(selectedTagQueries);
}, [selectedTagQueries]);
const handleFocus = (): void => {
if (state.value === 'Idle') {
send('NEXT');
}
};
const handleBlur = useCallback((): void => {
send('onBlur');
}, [send]);
useEffect(() => {
handleBlur();
}, [handleBlur, metricName]);
const handleChange = (value: never | string[]): void => {
if (!optionsData.mode) {
setStaging((prevStaging) => [...prevStaging, String(value)]);
setSelectedValues([]);
send('NEXT');
return;
}
if (
state.value === 'TagValue' &&
SingleValueOperators.includes(staging[staging.length - 1]) &&
Array.isArray(value)
) {
setSelectedValues([value[value.length - 1]]);
return;
}
setSelectedValues([...value]);
};
const handleClose = (id: string): void => {
dispatchQueries(queries.filter((queryData) => queryData.id !== id));
};
const handleClearAll = (): void => {
send('RESET');
dispatchQueries([]);
setStaging([]);
setSelectedValues([]);
};
return (
<SearchContainer isDarkMode={isDarkMode}>
<div style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
{queries.length > 0 &&
map(
queries,
(query): JSX.Element => (
<QueryChip key={query.id} queryData={query} onClose={handleClose} />
),
)}
</div>
<div>
{map(staging, (item) => (
<QueryChipItem key={uuid()}>{item}</QueryChipItem>
))}
</div>
<div style={{ display: 'flex', width: '100%' }}>
<Select
disabled={!metricName}
placeholder={`Select ${
state.value === 'Idle' ? 'Tag Key Pair' : state.value
}`}
onChange={handleChange}
bordered={false}
value={selectedValues as never}
style={{ flex: 1 }}
options={optionsData.options}
mode={optionsData?.mode}
showArrow={false}
onFocus={handleFocus}
onBlur={handleBlur}
notFoundContent={
loading ? (
<span>
<Spin size="small" /> Loading...{' '}
</span>
) : (
<span>
No resource attributes available to filter. Please refer docs to send
attributes.
</span>
)
}
/>
{queries.length || staging.length || selectedValues.length ? (
<Button
onClick={handleClearAll}
icon={<CloseCircleFilled />}
type="text"
/>
) : null}
</div>
</SearchContainer>
);
}
export default MetricTagKeyFilter;

View File

@ -1,30 +0,0 @@
import { grey } from '@ant-design/colors';
import { Tag } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div<{
isDarkMode: boolean;
disabled?: boolean;
}>`
background: ${({ isDarkMode }): string => (isDarkMode ? '#000' : '#fff')};
flex: 1;
display: flex;
flex-direction: column;
padding: 0.2rem;
border: 1px solid #ccc5;
${({ disabled }): string => (disabled ? `cursor: not-allowed;` : '')}
`;
export const QueryChipContainer = styled.span`
display: flex;
align-items: center;
margin-right: 0.5rem;
&:hover {
& > * {
background: ${grey.primary}44;
}
}
`;
export const QueryChipItem = styled(Tag)`
margin-right: 0.1rem;
`;

View File

@ -1,18 +0,0 @@
export interface IOption {
label: string;
value: string;
}
export interface IMetricBuilderTagKeyQuery {
id: string;
tagKey: string;
operator: string;
tagValue: string[];
}
export interface ITagKeyValueQuery {
id: string;
key: string;
op: string;
value: string[];
}

View File

@ -1,55 +0,0 @@
import {
getResourceAttributesTagKeys,
getResourceAttributesTagValues,
} from 'api/metrics/getResourceAttributes';
import { v4 as uuid } from 'uuid';
import { TagKeyOperator } from '../../Options';
import { IOption, ITagKeyValueQuery } from './types';
export const OperatorSchema: IOption[] = TagKeyOperator;
export const GetTagKeys = async (metricName: string): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagKeys({ metricName });
if (!payload || !payload?.data) {
return [];
}
return payload.data.map((tagKey: string) => ({
label: tagKey,
value: tagKey,
}));
};
export const GetTagValues = async (
tagKey: string,
metricName: string,
): Promise<IOption[]> => {
const { payload } = await getResourceAttributesTagValues({
tagKey,
metricName,
});
if (!payload || !payload?.data) {
return [];
}
return payload.data.map((tagValue: string) => ({
label: tagValue,
value: tagValue,
}));
};
export const createQuery = (
selectedItems: Array<string | string[]> = [],
): ITagKeyValueQuery | null => {
if (selectedItems.length === 3) {
return {
id: uuid().slice(0, 8),
key: typeof selectedItems[0] === 'string' ? selectedItems[0] : '',
op: typeof selectedItems[1] === 'string' ? selectedItems[1] : '',
value: selectedItems[2] as string[],
};
}
return null;
};
export const SingleValueOperators = ['LIKE', 'NLIKE'];

View File

@ -1,52 +0,0 @@
import { Input } from 'antd';
import React from 'react';
import { IMetricsBuilderFormula } from 'types/api/dashboard/getAll';
import QueryHeader from '../QueryHeader';
import { IQueryBuilderFormulaHandleChange } from './types';
const { TextArea } = Input;
interface IMetricsBuilderFormulaProps {
formulaData: IMetricsBuilderFormula;
formulaIndex: number | string;
handleFormulaChange: (args: IQueryBuilderFormulaHandleChange) => void;
}
function MetricsBuilderFormula({
formulaData,
formulaIndex,
handleFormulaChange,
}: IMetricsBuilderFormulaProps): JSX.Element {
return (
<QueryHeader
name={formulaData.name}
disabled={formulaData.disabled}
onDisable={(): void =>
handleFormulaChange({ formulaIndex, toggleDisable: true })
}
onDelete={(): void => {
handleFormulaChange({ formulaIndex, toggleDelete: true });
}}
>
<TextArea
onChange={(event): void =>
handleFormulaChange({ formulaIndex, expression: event.target.value })
}
size="middle"
defaultValue={formulaData.expression}
style={{ marginBottom: '0.5rem' }}
rows={2}
/>
<Input
onChange={(event): void => {
handleFormulaChange({ formulaIndex, legend: event.target.value });
}}
size="middle"
defaultValue={formulaData.legend}
addonBefore="Legend Format"
/>
</QueryHeader>
);
}
export default MetricsBuilderFormula;

View File

@ -1,191 +0,0 @@
/* eslint-disable */
// TODO: fix it after merge actual functionality
// @ts-nocheck
import { PlusOutlined } from '@ant-design/icons';
import {
QueryBuilderFormulaTemplate,
QueryBuilderQueryTemplate,
} from 'constants/dashboard';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { useNotifications } from 'hooks/useNotifications';
import GetFormulaName from 'lib/query/GetFormulaName';
import GetQueryName from 'lib/query/GetQueryName';
import React from 'react';
import { Query } from 'types/api/dashboard/getAll';
import {
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME,
WIDGET_QUERY_BUILDER_QUERY_KEY_NAME,
} from '../../constants';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import MetricsBuilderFormula from './formula';
import MetricsBuilder from './query';
import {
IQueryBuilderFormulaHandleChange,
IQueryBuilderQueryHandleChange,
} from './types';
import { canCreateQueryAndFormula } from './utils';
interface IQueryBuilderQueryContainerProps {
queryData: Query;
updateQueryData: (args: IHandleUpdatedQuery) => void;
metricsBuilderQueries: Query['metricsBuilder'];
selectedGraph: GRAPH_TYPES;
}
function QueryBuilderQueryContainer({
queryData,
updateQueryData,
metricsBuilderQueries,
selectedGraph,
}: IQueryBuilderQueryContainerProps): JSX.Element | null {
const { notifications } = useNotifications();
const handleQueryBuilderQueryChange = ({
queryIndex,
aggregateFunction,
metricName,
tagFilters,
groupBy,
legend,
toggleDisable,
toggleDelete,
reduceTo,
}: IQueryBuilderQueryHandleChange): void => {
const allQueries =
queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder;
const currentIndexQuery = allQueries[queryIndex as number];
if (aggregateFunction) {
currentIndexQuery.aggregateOperator = aggregateFunction;
}
if (metricName !== undefined) {
currentIndexQuery.metricName = metricName;
}
if (tagFilters) {
currentIndexQuery.tagFilters.items = tagFilters;
}
if (groupBy) {
currentIndexQuery.groupBy = groupBy;
}
if (reduceTo) {
currentIndexQuery.reduceTo = reduceTo;
}
if (legend !== undefined) {
currentIndexQuery.legend = legend;
}
if (toggleDisable) {
currentIndexQuery.disabled = !currentIndexQuery.disabled;
}
if (toggleDelete) {
allQueries.splice(queryIndex as number, 1);
}
updateQueryData({ updatedQuery: { ...queryData } });
};
const handleQueryBuilderFormulaChange = ({
formulaIndex,
expression,
legend,
toggleDisable,
toggleDelete,
}: IQueryBuilderFormulaHandleChange): void => {
const allFormulas =
queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
];
const currentIndexFormula = allFormulas[formulaIndex as number];
if (expression !== undefined) {
currentIndexFormula.expression = expression;
}
if (legend !== undefined) {
currentIndexFormula.legend = legend;
}
if (toggleDisable) {
currentIndexFormula.disabled = !currentIndexFormula.disabled;
}
if (toggleDelete) {
allFormulas.splice(formulaIndex as number, 1);
}
updateQueryData({ updatedQuery: { ...queryData } });
};
const addQueryHandler = (): void => {
if (!canCreateQueryAndFormula(queryData)) {
notifications.error({
message:
'Unable to create query. You can create at max 10 queries and formulae.',
});
return;
}
queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder.push({
name:
GetQueryName(queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder) ||
'',
...QueryBuilderQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });
};
const addFormulaHandler = (): void => {
if (!canCreateQueryAndFormula(queryData)) {
notifications.error({
message:
'Unable to create formula. You can create at max 10 queries and formulae.',
});
return;
}
queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
].push({
name:
GetFormulaName(
queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
],
) || '',
...QueryBuilderFormulaTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });
};
if (!metricsBuilderQueries) {
return null;
}
return (
<>
{metricsBuilderQueries.queryBuilder.map((q, idx) => (
<MetricsBuilder
key={q.name}
queryIndex={idx}
queryData={q}
handleQueryChange={handleQueryBuilderQueryChange}
selectedGraph={selectedGraph}
/>
))}
<QueryButton onClick={addQueryHandler} icon={<PlusOutlined />}>
Query
</QueryButton>
<div style={{ marginTop: '1rem' }}>
{metricsBuilderQueries.formulas.map((f, idx) => (
<MetricsBuilderFormula
key={f.name}
formulaIndex={idx}
formulaData={f}
handleFormulaChange={handleQueryBuilderFormulaChange}
/>
))}
<QueryButton onClick={addFormulaHandler} icon={<PlusOutlined />}>
Formula
</QueryButton>
</div>
</>
);
}
export default QueryBuilderQueryContainer;

View File

@ -1,215 +0,0 @@
import { AutoComplete, Col, Input, Row, Select, Spin } from 'antd';
import { getMetricName } from 'api/metrics/getMetricName';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import React, { useEffect, useState } from 'react';
import { IMetricsBuilderQuery } from 'types/api/dashboard/getAll';
import { EReduceOperator } from 'types/common/dashboard';
import { AggregateFunctions } from '../Options';
import QueryHeader from '../QueryHeader';
import MetricTagKeyFilter from './MetricTagKeyFilter';
import { IOption } from './MetricTagKeyFilter/types';
import { GetTagKeys } from './MetricTagKeyFilter/utils';
import { IQueryBuilderQueryHandleChange } from './types';
const { Option } = Select;
interface IMetricsBuilderProps {
queryIndex: number | string;
selectedGraph: GRAPH_TYPES;
queryData: IMetricsBuilderQuery;
handleQueryChange: (args: IQueryBuilderQueryHandleChange) => void;
}
function MetricsBuilder({
queryIndex,
selectedGraph,
queryData,
handleQueryChange,
}: IMetricsBuilderProps): JSX.Element {
const [groupByOptions, setGroupByOptions] = useState<IOption[]>([]);
const [metricName, setMetricName] = useState<string | null>(
queryData.metricName,
);
const [metricNameList, setMetricNameList] = useState<string[]>([]);
const [metricNameLoading, setMetricNameLoading] = useState(false);
const handleMetricNameSelect = (e: string): void => {
handleQueryChange({ queryIndex, metricName: e });
setMetricName(e);
};
const handleMetricNameSearch = async (searchQuery = ''): Promise<void> => {
handleMetricNameSelect(searchQuery);
setMetricNameList([]);
setMetricNameLoading(true);
const { payload } = await getMetricName(searchQuery);
setMetricNameLoading(false);
if (!payload || !payload.data) {
return;
}
setMetricNameList(payload.data);
};
const [aggregateFunctionList, setAggregateFunctionList] = useState(
AggregateFunctions,
);
const handleAggregateFunctionsSearch = (searchQuery = ''): void => {
setAggregateFunctionList(
AggregateFunctions.filter(({ label }) =>
label.includes(searchQuery.toUpperCase()),
) || [],
);
};
useEffect(() => {
GetTagKeys(metricName || '').then((tagKeys) => {
setGroupByOptions(tagKeys);
});
}, [metricName]);
return (
<QueryHeader
name={queryData.name}
disabled={queryData.disabled}
onDisable={(): void =>
handleQueryChange({ queryIndex, toggleDisable: true })
}
onDelete={(): void => {
handleQueryChange({ queryIndex, toggleDelete: true });
}}
>
<div style={{ display: 'flex', flexDirection: 'column', padding: '0.5rem' }}>
<div>
<Select
onChange={(e): void =>
handleQueryChange({ queryIndex, aggregateFunction: e })
}
defaultValue={queryData.aggregateOperator || AggregateFunctions[0]}
style={{ minWidth: 150 }}
options={aggregateFunctionList}
showSearch
onSearch={handleAggregateFunctionsSearch}
filterOption={false}
/>
</div>
<Row style={{ gap: '3%', margin: '0.5rem 0' }}>
<Row style={{ flex: 2, gap: '3%' }}>
<Select
defaultValue="metrics"
showArrow={false}
dropdownStyle={{ display: 'none' }}
>
<Option value="metrics">Metrics</Option>
</Select>
<AutoComplete
showSearch
placeholder="Metric Name (Start typing to get suggestions)"
style={{ flex: 1, minWidth: 200 }}
showArrow={false}
filterOption={false}
onSearch={handleMetricNameSearch}
notFoundContent={metricNameLoading ? <Spin size="small" /> : null}
options={metricNameList.map((option) => ({
label: option,
value: option,
}))}
defaultValue={queryData.metricName}
value={metricName}
onSelect={handleMetricNameSelect}
/>
</Row>
<Col style={{ flex: 3 }}>
<Row style={{ gap: '3%', marginBottom: '1rem' }}>
<Select
defaultValue="WHERE"
showArrow={false}
dropdownStyle={{ display: 'none' }}
>
<Option value="WHERE">WHERE</Option>
</Select>
<MetricTagKeyFilter
metricName={metricName}
selectedTagFilters={queryData.tagFilters.items}
onSetQuery={(
updatedTagFilters: IMetricsBuilderQuery['tagFilters']['items'],
): void =>
handleQueryChange({ queryIndex, tagFilters: updatedTagFilters })
}
/>
</Row>
<Row style={{ gap: '3%', marginBottom: '1rem' }}>
{selectedGraph === PANEL_TYPES.TIME_SERIES ? (
<>
{' '}
<Select
defaultValue="GROUP BY"
showArrow={false}
dropdownStyle={{ display: 'none' }}
>
<Option value="GROUP BY">GROUP BY</Option>
</Select>
<Select
mode="multiple"
showSearch
style={{ flex: 1 }}
defaultActiveFirstOption={false}
filterOption={false}
notFoundContent={metricNameLoading ? <Spin size="small" /> : null}
options={groupByOptions}
defaultValue={queryData.groupBy}
onChange={(e): void => {
handleQueryChange({ queryIndex, groupBy: e });
}}
/>
</>
) : (
<>
<Select
defaultValue="REDUCE TO"
showArrow={false}
dropdownStyle={{ display: 'none' }}
>
<Option value="GROUP BY">REDUCE TO</Option>
</Select>
<Select
placeholder="Latest of values in timeframe"
style={{ flex: 1 }}
options={Object.keys(EReduceOperator)
.filter((op) => !(parseInt(op, 10) >= 0))
.map((op) => ({
label: op,
value: EReduceOperator[op as keyof typeof EReduceOperator],
}))}
defaultValue={
EReduceOperator[
(queryData.reduceTo as unknown) as keyof typeof EReduceOperator
]
}
onChange={(e): void => {
handleQueryChange({ queryIndex, reduceTo: e });
}}
/>
</>
)}
</Row>
</Col>
</Row>
<Row style={{ margin: '0.5rem 0' }}>
<Input
onChange={(e): void => {
handleQueryChange({ queryIndex, legend: e.target.value });
}}
size="middle"
defaultValue={queryData.legend}
addonBefore="Legend Format"
/>
</Row>
</div>
</QueryHeader>
);
}
export default MetricsBuilder;

View File

@ -1,24 +0,0 @@
import {
IMetricsBuilderFormula,
IMetricsBuilderQuery,
} from 'types/api/dashboard/getAll';
export interface IQueryBuilderQueryHandleChange {
queryIndex: number | string;
aggregateFunction?: IMetricsBuilderQuery['aggregateOperator'];
metricName?: IMetricsBuilderQuery['metricName'];
tagFilters?: IMetricsBuilderQuery['tagFilters']['items'];
groupBy?: IMetricsBuilderQuery['groupBy'];
legend?: IMetricsBuilderQuery['legend'];
toggleDisable?: boolean;
toggleDelete?: boolean;
reduceTo?: IMetricsBuilderQuery['reduceTo'];
}
export interface IQueryBuilderFormulaHandleChange {
formulaIndex: number | string;
expression?: IMetricsBuilderFormula['expression'];
toggleDisable?: IMetricsBuilderFormula['disabled'];
legend?: IMetricsBuilderFormula['legend'];
toggleDelete?: boolean;
}

View File

@ -1,12 +0,0 @@
import { Query } from 'types/api/dashboard/getAll';
import { WIDGET_QUERY_BUILDER_QUERY_KEY_NAME } from '../../constants';
const QUERY_AND_FORMULA_LIMIT = 10;
export const canCreateQueryAndFormula = (query: Query): boolean => {
const queries = query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryData;
const formulas = query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryFormulas;
return queries.length + formulas.length < QUERY_AND_FORMULA_LIMIT;
};

View File

@ -1,10 +0,0 @@
import { EQueryType } from 'types/common/dashboard';
export const WIDGET_PROMQL_QUERY_KEY_NAME = EQueryType.PROM;
export const WIDGET_CLICKHOUSE_QUERY_KEY_NAME = EQueryType.CLICKHOUSE;
export const WIDGET_QUERY_BUILDER_QUERY_KEY_NAME = EQueryType.QUERY_BUILDER;
type TFormulas = 'formulas';
export const WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME: TFormulas = 'formulas';

View File

@ -20,10 +20,6 @@ import { EQueryType } from 'types/common/dashboard';
import DashboardReducer from 'types/reducer/dashboards';
import { v4 as uuid } from 'uuid';
import {
WIDGET_CLICKHOUSE_QUERY_KEY_NAME,
WIDGET_PROMQL_QUERY_KEY_NAME,
} from './constants';
import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
import { IHandleUpdatedQuery } from './types';
@ -126,7 +122,7 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
updateQueryData={({ updatedQuery }: IHandleUpdatedQuery): void => {
handleLocalQueryUpdate({ updatedQuery });
}}
clickHouseQueries={localQueryChanges[WIDGET_CLICKHOUSE_QUERY_KEY_NAME]}
clickHouseQueries={localQueryChanges[EQueryType.CLICKHOUSE]}
/>
),
},
@ -141,7 +137,7 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
updateQueryData={({ updatedQuery }: IHandleUpdatedQuery): void => {
handleLocalQueryUpdate({ updatedQuery });
}}
promQLQueries={localQueryChanges[WIDGET_PROMQL_QUERY_KEY_NAME]}
promQLQueries={localQueryChanges[EQueryType.PROM]}
/>
),
},

View File

@ -1,12 +1,6 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import {
IClickHouseQuery,
IMetricsBuilderFormula,
IMetricsBuilderQuery,
IPromQLQuery,
IQueryBuilderTagFilters,
} from 'types/api/dashboard/getAll';
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
import { IClickHouseQuery, IPromQLQuery } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
export interface ICompositeMetricQuery {
@ -32,44 +26,3 @@ export interface IPromQuery extends IPromQLQuery {
export interface IPromQueries {
[key: string]: IPromQuery;
}
export interface IBuilderQueries {
[key: string]: IBuilderQuery;
}
// IBuilderQuery combines IMetricQuery and IFormulaQuery
// for api calls
export interface IBuilderQuery
extends Omit<
IMetricQuery,
'aggregateOperator' | 'legend' | 'metricName' | 'tagFilters'
> {
aggregateOperator: EAggregateOperator | undefined;
disabled: boolean;
name: string;
legend?: string;
metricName: string | null;
groupBy?: string[];
expression?: string;
tagFilters?: IQueryBuilderTagFilters;
toggleDisable?: boolean;
toggleDelete?: boolean;
}
export interface IFormulaQueries {
[key: string]: IFormulaQuery;
}
export interface IFormulaQuery extends IMetricsBuilderFormula {
formulaOnly: boolean;
queryName: string;
}
export interface IMetricQueries {
[key: string]: IMetricQuery;
}
export interface IMetricQuery extends IMetricsBuilderQuery {
formulaOnly: boolean;
expression?: string;
queryName: string;
}

View File

@ -1,11 +1,7 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Layout } from 'react-grid-layout';
import {
EAggregateOperator,
EQueryType,
EReduceOperator,
} from 'types/common/dashboard';
import { EQueryType } from 'types/common/dashboard';
import { QueryBuilderData } from 'types/common/queryBuilder';
import { QueryData } from '../widgets/getQuery';
@ -97,28 +93,6 @@ export interface Query {
clickhouse_sql: IClickHouseQuery[];
}
export interface IMetricsBuilderFormula {
expression: string;
disabled: boolean;
name: string;
legend: string;
}
export interface IMetricsBuilderQuery {
aggregateOperator: EAggregateOperator;
disabled: boolean;
name: string;
legend: string;
metricName: string | null;
groupBy?: string[];
tagFilters: IQueryBuilderTagFilters;
reduceTo?: EReduceOperator;
}
export interface IQueryBuilderTagFilters {
op: string;
items: IQueryBuilderTagFilterItems[] | [];
}
export interface IClickHouseQuery {
name: string;
rawQuery: string;

View File

@ -3,47 +3,3 @@ export enum EQueryType {
CLICKHOUSE = 'clickhouse_sql',
PROM = 'promql',
}
export enum EAggregateOperator {
NOOP = 1,
COUNT = 2,
COUNT_DISTINCT = 3,
SUM = 4,
AVG = 5,
MAX = 6,
MIN = 7,
P05 = 8,
P10 = 9,
P20 = 10,
P25 = 11,
P50 = 12,
P75 = 13,
P90 = 14,
P95 = 15,
P99 = 16,
RATE = 17,
SUM_RATE = 18,
// leaving gap for possible future {X}_RATE
RATE_SUM = 22,
RATE_AVG = 23,
RATE_MAX = 24,
RATE_MIN = 25,
HIST_QUANTILE_50 = 26,
HIST_QUANTILE_75 = 27,
HIST_QUANTILE_90 = 28,
HIST_QUANTILE_95 = 29,
HIST_QUANTILE_99 = 30,
}
export enum EPanelType {
GRAPH = 'graph',
VALUE = 'value',
}
export enum EReduceOperator {
'Latest of values in timeframe' = 1, // LAST
'Sum of values in timeframe', // SUM
'Average of values in timeframe', // AVG
'Max of values in timeframe', // MAX
'Min of values in timeframe', // MIN
}