feat: add query builder to the alerts (#2657)

* fix: having value data type

* feat: connect new builder to dashboard

* Fix/query builder filters (#2623)

* feat: rename query data type

* fix: remove reset of groupBy

* fix: filters search

* fix: calls autocomplete times

* fix: response mapper

* fix: removee unnecessary field

* fix: no check ts types for old query builder

* fix: disable check utils old builder

* feat: add query builder to the alerts

* fix: alert response integration with query builder

* fix: validation of query builder rules

* fix: rules query builder

* fix: filter value with similar keys

* fix: null values for options

* fix: query builder disabled when exist formula

* fix: removing filter key with underscore

* feat: add builder data to metric application (#2665)

* feat: add builder data to metric application

* fix: query types to single variant

* fix: formula legend formatting

* fix: argumant name

* fix: date for graph

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>

* fix: pipeline

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-05-10 19:40:27 +03:00 committed by GitHub
parent f7cd0d4934
commit 8679f2c37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 726 additions and 924 deletions

View File

@ -0,0 +1,9 @@
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { DataSource } from 'types/common/queryBuilder';
export const ALERTS_DATA_SOURCE_MAP: Record<AlertTypes, DataSource> = {
[AlertTypes.METRICS_BASED_ALERT]: DataSource.METRICS,
[AlertTypes.LOGS_BASED_ALERT]: DataSource.LOGS,
[AlertTypes.TRACES_BASED_ALERT]: DataSource.TRACES,
[AlertTypes.EXCEPTIONS_BASED_ALERT]: DataSource.TRACES,
};

View File

@ -86,7 +86,7 @@ export const initialAggregateAttribute: IBuilderQuery['aggregateAttribute'] = {
export const initialQueryBuilderFormValues: IBuilderQuery = {
dataSource: DataSource.METRICS,
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
aggregateOperator: Object.values(MetricAggregateOperator)[0],
aggregateOperator: MetricAggregateOperator.NOOP,
aggregateAttribute: initialAggregateAttribute,
tagFilters: { items: [], op: 'AND' },
expression: createNewBuilderItemName({

View File

@ -0,0 +1,5 @@
export const FORMULA_REGEXP = /F\d+/;
export const HAVING_FILTER_REGEXP = /^[-\d.,\s]+$/;
export const TYPE_ADDON_REGEXP = /_(.+)/;

View File

@ -1,3 +1,7 @@
import {
initialQueryBuilderFormValues,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
AlertDef,
@ -5,6 +9,12 @@ import {
defaultEvalWindow,
defaultMatchType,
} from 'types/api/alerts/def';
import { EQueryType } from 'types/common/dashboard';
import {
DataSource,
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
const defaultAlertDescription =
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})';
@ -19,28 +29,16 @@ const defaultAnnotations = {
export const alertDefaults: AlertDef = {
alertType: AlertTypes.METRICS_BASED_ALERT,
condition: {
compositeMetricQuery: {
compositeQuery: {
builderQueries: {
A: {
queryName: 'A',
name: 'A',
formulaOnly: false,
metricName: '',
tagFilters: {
op: 'AND',
items: [],
},
groupBy: [],
aggregateOperator: 1,
expression: 'A',
disabled: false,
toggleDisable: false,
toggleDelete: false,
...initialQueryBuilderFormValues,
},
},
promQueries: {},
chQueries: {},
queryType: 1,
queryType: EQueryType.QUERY_BUILDER,
panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: defaultMatchType,
@ -55,23 +53,12 @@ export const alertDefaults: AlertDef = {
export const logAlertDefaults: AlertDef = {
alertType: AlertTypes.LOGS_BASED_ALERT,
condition: {
compositeMetricQuery: {
compositeQuery: {
builderQueries: {
A: {
queryName: 'A',
name: 'A',
formulaOnly: false,
metricName: '',
tagFilters: {
op: 'AND',
items: [],
},
groupBy: [],
aggregateOperator: 1,
expression: 'A',
disabled: false,
toggleDisable: false,
toggleDelete: false,
...initialQueryBuilderFormValues,
aggregateOperator: LogsAggregatorOperator.COUNT,
dataSource: DataSource.LOGS,
},
},
promQueries: {},
@ -84,7 +71,8 @@ export const logAlertDefaults: AlertDef = {
disabled: false,
},
},
queryType: 2,
queryType: EQueryType.CLICKHOUSE,
panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: '4',
@ -100,23 +88,12 @@ export const logAlertDefaults: AlertDef = {
export const traceAlertDefaults: AlertDef = {
alertType: AlertTypes.TRACES_BASED_ALERT,
condition: {
compositeMetricQuery: {
compositeQuery: {
builderQueries: {
A: {
queryName: 'A',
name: 'A',
formulaOnly: false,
metricName: '',
tagFilters: {
op: 'AND',
items: [],
},
groupBy: [],
aggregateOperator: 1,
expression: 'A',
disabled: false,
toggleDisable: false,
toggleDelete: false,
...initialQueryBuilderFormValues,
aggregateOperator: TracesAggregatorOperator.COUNT,
dataSource: DataSource.TRACES,
},
},
promQueries: {},
@ -129,7 +106,8 @@ export const traceAlertDefaults: AlertDef = {
disabled: false,
},
},
queryType: 2,
queryType: EQueryType.CLICKHOUSE,
panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: '4',
@ -145,23 +123,12 @@ export const traceAlertDefaults: AlertDef = {
export const exceptionAlertDefaults: AlertDef = {
alertType: AlertTypes.EXCEPTIONS_BASED_ALERT,
condition: {
compositeMetricQuery: {
compositeQuery: {
builderQueries: {
A: {
queryName: 'A',
name: 'A',
formulaOnly: false,
metricName: '',
tagFilters: {
op: 'AND',
items: [],
},
groupBy: [],
aggregateOperator: 1,
expression: 'A',
disabled: false,
toggleDisable: false,
toggleDelete: false,
...initialQueryBuilderFormValues,
aggregateOperator: TracesAggregatorOperator.COUNT,
dataSource: DataSource.TRACES,
},
},
promQueries: {},
@ -174,7 +141,8 @@ export const exceptionAlertDefaults: AlertDef = {
disabled: false,
},
},
queryType: 2,
queryType: EQueryType.CLICKHOUSE,
panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: '4',

View File

@ -60,10 +60,11 @@ function ChartPreview({
switch (query?.queryType) {
case EQueryType.PROM:
return query.promQL?.length > 0 && query.promQL[0].query !== '';
return query.promql?.length > 0 && query.promql[0].query !== '';
case EQueryType.CLICKHOUSE:
return (
query.clickHouse?.length > 0 && query.clickHouse[0].rawQuery?.length > 0
query.clickhouse_sql?.length > 0 &&
query.clickhouse_sql[0].rawQuery?.length > 0
);
case EQueryType.QUERY_BUILDER:
return (
@ -84,13 +85,13 @@ function ChartPreview({
queryFn: () =>
GetMetricQueryRange({
query: query || {
queryType: 1,
promQL: [],
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: {
queryFormulas: [],
queryData: [],
},
clickHouse: [],
clickhouse_sql: [],
},
globalSelectedInterval: selectedInterval,
graphType,

View File

@ -1,36 +1,20 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, Tabs } from 'antd';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { PANEL_TYPES } from 'constants/queryBuilder';
import MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula';
import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query';
import {
IQueryBuilderFormulaHandleChange,
IQueryBuilderQueryHandleChange,
} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
import { useNotifications } from 'hooks/useNotifications';
import React, { useCallback } from 'react';
import { QueryBuilder } from 'container/QueryBuilder';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
IChQueries,
IFormulaQueries,
IMetricQueries,
IPromQueries,
} from 'types/api/alerts/compositeQuery';
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
import { EQueryType } from 'types/common/dashboard';
import ChQuerySection from './ChQuerySection';
import PromqlSection from './PromqlSection';
import { FormContainer, QueryButton, StepHeading } from './styles';
import { toIMetricsBuilderQuery } from './utils';
import { FormContainer, StepHeading } from './styles';
function QuerySection({
queryCategory,
setQueryCategory,
metricQueries,
setMetricQueries,
formulaQueries,
setFormulaQueries,
promQueries,
setPromQueries,
chQueries,
@ -41,9 +25,9 @@ function QuerySection({
// init namespace for translations
const { t } = useTranslation('alerts');
const handleQueryCategoryChange = (s: string): void => {
const handleQueryCategoryChange = (queryType: string): void => {
if (
parseInt(s, 10) === EQueryType.PROM &&
queryType === EQueryType.PROM &&
(!promQueries || Object.keys(promQueries).length === 0)
) {
setPromQueries({
@ -58,7 +42,7 @@ function QuerySection({
}
if (
parseInt(s, 10) === EQueryType.CLICKHOUSE &&
queryType === EQueryType.CLICKHOUSE &&
(!chQueries || Object.keys(chQueries).length === 0)
) {
setChQueries({
@ -71,148 +55,9 @@ function QuerySection({
},
});
}
setQueryCategory(parseInt(s, 10));
setQueryCategory(queryType as EQueryType);
};
const getNextQueryLabel = useCallback((): string => {
let maxAscii = 0;
Object.keys(metricQueries).forEach((key) => {
const n = key.charCodeAt(0);
if (n > maxAscii) {
maxAscii = n - 64;
}
});
return String.fromCharCode(64 + maxAscii + 1);
}, [metricQueries]);
const handleFormulaChange = ({
formulaIndex,
expression,
legend,
toggleDisable,
toggleDelete,
}: IQueryBuilderFormulaHandleChange): void => {
const allFormulas = formulaQueries;
const current = allFormulas[formulaIndex];
if (expression !== undefined) {
current.expression = expression;
}
if (legend !== undefined) {
current.legend = legend;
}
if (toggleDisable) {
current.disabled = !current.disabled;
}
if (toggleDelete) {
delete allFormulas[formulaIndex];
} else {
allFormulas[formulaIndex] = current;
}
setFormulaQueries({
...allFormulas,
});
};
const handleMetricQueryChange = ({
queryIndex,
aggregateFunction,
metricName,
tagFilters,
groupBy,
legend,
toggleDisable,
toggleDelete,
}: IQueryBuilderQueryHandleChange): void => {
const allQueries = metricQueries;
const current = metricQueries[queryIndex];
if (aggregateFunction) {
current.aggregateOperator = aggregateFunction;
}
if (metricName) {
current.metricName = metricName;
}
if (tagFilters && current.tagFilters) {
current.tagFilters.items = tagFilters;
}
if (legend) {
current.legend = legend;
}
if (groupBy) {
current.groupBy = groupBy;
}
if (toggleDisable) {
current.disabled = !current.disabled;
}
if (toggleDelete) {
delete allQueries[queryIndex];
} else {
allQueries[queryIndex] = current;
}
setMetricQueries({
...allQueries,
});
};
const { notifications } = useNotifications();
const addMetricQuery = useCallback(() => {
if (Object.keys(metricQueries).length > 5) {
notifications.error({
message: t('metric_query_max_limit'),
});
return;
}
const queryLabel = getNextQueryLabel();
const queries = metricQueries;
queries[queryLabel] = {
name: queryLabel,
queryName: queryLabel,
metricName: '',
formulaOnly: false,
aggregateOperator: EAggregateOperator.NOOP,
legend: '',
tagFilters: {
op: 'AND',
items: [],
},
groupBy: [],
disabled: false,
expression: queryLabel,
};
setMetricQueries({ ...queries });
}, [t, getNextQueryLabel, metricQueries, setMetricQueries, notifications]);
const addFormula = useCallback(() => {
// defaulting to F1 as only one formula is supported
// in alert definition
const queryLabel = 'F1';
const formulas = formulaQueries;
formulas[queryLabel] = {
queryName: queryLabel,
name: queryLabel,
formulaOnly: true,
expression: 'A',
disabled: false,
legend: '',
};
setFormulaQueries({ ...formulas });
}, [formulaQueries, setFormulaQueries]);
const renderPromqlUI = (): JSX.Element => (
<PromqlSection promQueries={promQueries} setPromQueries={setPromQueries} />
);
@ -221,61 +66,14 @@ function QuerySection({
<ChQuerySection chQueries={chQueries} setChQueries={setChQueries} />
);
const renderFormulaButton = (): JSX.Element => (
<QueryButton onClick={addFormula} icon={<PlusOutlined />}>
{t('button_formula')}
</QueryButton>
);
const renderQueryButton = (): JSX.Element => (
<QueryButton onClick={addMetricQuery} icon={<PlusOutlined />}>
{t('button_query')}
</QueryButton>
);
const renderMetricUI = (): JSX.Element => (
<div>
{metricQueries &&
Object.keys(metricQueries).map((key: string) => {
// todo(amol): need to handle this in fetch
const current = metricQueries[key];
current.name = key;
return (
<MetricsBuilder
key={key}
queryIndex={key}
queryData={toIMetricsBuilderQuery(current)}
selectedGraph={PANEL_TYPES.TIME_SERIES}
handleQueryChange={handleMetricQueryChange}
/>
);
})}
{queryCategory !== EQueryType.PROM && renderQueryButton()}
<div style={{ marginTop: '1rem' }}>
{formulaQueries &&
Object.keys(formulaQueries).map((key: string) => {
// todo(amol): need to handle this in fetch
const current = formulaQueries[key];
current.name = key;
return (
<MetricsBuilderFormula
key={key}
formulaIndex={key}
formulaData={current}
handleFormulaChange={handleFormulaChange}
/>
);
})}
{queryCategory === EQueryType.QUERY_BUILDER &&
(!formulaQueries || Object.keys(formulaQueries).length === 0) &&
metricQueries &&
Object.keys(metricQueries).length > 0 &&
renderFormulaButton()}
</div>
</div>
<QueryBuilder
panelType={PANEL_TYPES.TIME_SERIES}
config={{
queryVariant: 'static',
initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType],
}}
/>
);
const handleRunQuery = (): void => {
@ -285,19 +83,18 @@ function QuerySection({
const tabs = [
{
label: t('tab_qb'),
key: EQueryType.QUERY_BUILDER.toString(),
disabled: true,
key: EQueryType.QUERY_BUILDER,
},
{
label: t('tab_chquery'),
key: EQueryType.CLICKHOUSE.toString(),
key: EQueryType.CLICKHOUSE,
},
];
const items = [
{ label: t('tab_qb'), key: EQueryType.QUERY_BUILDER.toString() },
{ label: t('tab_chquery'), key: EQueryType.CLICKHOUSE.toString() },
{ label: t('tab_promql'), key: EQueryType.PROM.toString() },
{ label: t('tab_qb'), key: EQueryType.QUERY_BUILDER },
{ label: t('tab_chquery'), key: EQueryType.CLICKHOUSE },
{ label: t('tab_promql'), key: EQueryType.PROM },
];
const renderTabs = (typ: AlertTypes): JSX.Element | null => {
@ -309,16 +106,14 @@ function QuerySection({
<Tabs
type="card"
style={{ width: '100%' }}
defaultActiveKey={EQueryType.CLICKHOUSE.toString()}
activeKey={queryCategory.toString()}
defaultActiveKey={EQueryType.QUERY_BUILDER}
activeKey={queryCategory}
onChange={handleQueryCategoryChange}
tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
{queryCategory === EQueryType.CLICKHOUSE && (
<Button type="primary" onClick={handleRunQuery}>
Run Query
</Button>
)}
<Button type="primary" onClick={handleRunQuery}>
Run Query
</Button>
</span>
}
items={tabs}
@ -330,16 +125,14 @@ function QuerySection({
<Tabs
type="card"
style={{ width: '100%' }}
defaultActiveKey={EQueryType.QUERY_BUILDER.toString()}
activeKey={queryCategory.toString()}
defaultActiveKey={EQueryType.QUERY_BUILDER}
activeKey={queryCategory}
onChange={handleQueryCategoryChange}
tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
{queryCategory === EQueryType.CLICKHOUSE && (
<Button type="primary" onClick={handleRunQuery}>
Run Query
</Button>
)}
<Button type="primary" onClick={handleRunQuery}>
Run Query
</Button>
</span>
}
items={items}
@ -373,10 +166,6 @@ function QuerySection({
interface QuerySectionProps {
queryCategory: EQueryType;
setQueryCategory: (n: EQueryType) => void;
metricQueries: IMetricQueries;
setMetricQueries: (b: IMetricQueries) => void;
formulaQueries: IFormulaQueries;
setFormulaQueries: (b: IFormulaQueries) => void;
promQueries: IPromQueries;
setPromQueries: (p: IPromQueries) => void;
chQueries: IChQueries;

View File

@ -5,18 +5,16 @@ import testAlertApi from 'api/alerts/testAlert';
import ROUTES from 'constants/routes';
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
IChQueries,
IFormulaQueries,
IMetricQueries,
IPromQueries,
} from 'types/api/alerts/compositeQuery';
import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
import {
AlertDef,
defaultEvalWindow,
@ -36,15 +34,8 @@ import {
PanelContainer,
StyledLeftContainer,
} from './styles';
import useDebounce from './useDebounce';
import UserGuide from './UserGuide';
import {
prepareBuilderQueries,
prepareStagedQuery,
toChartInterval,
toFormulaQueries,
toMetricQueries,
} from './utils';
import { prepareStagedQuery, toChartInterval } from './utils';
function FormAlertRules({
alertType,
@ -55,33 +46,21 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const { queryBuilderData, initQueryBuilderData } = useQueryBuilder();
// use query client
const ruleCache = useQueryClient();
const [loading, setLoading] = useState(false);
// queryRunId helps to override of query caching for clickhouse query
// tab. A random string will be assigned for each execution
const [runQueryId, setRunQueryId] = useState<string>();
// alertDef holds the form values to be posted
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
// initQuery contains initial query when component was mounted
const initQuery = initialValue?.condition?.compositeMetricQuery;
const initQuery = initialValue.condition.compositeQuery;
const [queryCategory, setQueryCategory] = useState<EQueryType>(
initQuery?.queryType,
);
// local state to handle metric queries
const [metricQueries, setMetricQueries] = useState<IMetricQueries>(
toMetricQueries(initQuery?.builderQueries),
);
// local state to handle formula queries
const [formulaQueries, setFormulaQueries] = useState<IFormulaQueries>(
toFormulaQueries(initQuery?.builderQueries),
initQuery.queryType,
);
// local state to handle promql queries
@ -106,43 +85,31 @@ function FormAlertRules({
// run query button is provided.
const [manualStagedQuery, setManualStagedQuery] = useState<StagedQuery>();
// delay to reduce load on backend api with auto-run query. only for clickhouse
// queries we have manual run, hence both debounce and debounceStagedQuery are not required
const debounceDelay = queryCategory !== EQueryType.CLICKHOUSE ? 1000 : 0;
// debounce query to delay backend api call and chart update.
// used in query builder and promql tabs to enable auto-refresh
// of chart on user edit
const debouncedStagedQuery = useDebounce(stagedQuery, debounceDelay);
// this use effect initiates staged query and
// other queries based on server data.
// useful when fetching of initial values (from api)
// is delayed
useEffect(() => {
const initQuery = initialValue?.condition?.compositeMetricQuery;
const typ = initQuery?.queryType;
const initQuery = initialValue?.condition?.compositeQuery;
const type = initQuery.queryType;
// extract metric query from builderQueries
const mq = toMetricQueries(initQuery?.builderQueries);
// extract formula query from builderQueries
const fq = toFormulaQueries(initQuery?.builderQueries);
const builderData = mapQueryDataFromApi(
initialValue?.condition?.compositeQuery?.builderQueries || {},
);
// prepare staged query
const sq = prepareStagedQuery(
typ,
mq,
fq,
type,
builderData.queryData,
builderData.queryFormulas,
initQuery?.promQueries,
initQuery?.chQueries,
);
const pq = initQuery?.promQueries;
const chq = initQuery?.chQueries;
setQueryCategory(typ);
setMetricQueries(mq);
setFormulaQueries(fq);
setQueryCategory(type);
initQueryBuilderData(builderData);
setPromQueries(pq);
setStagedQuery(sq);
@ -151,7 +118,7 @@ function FormAlertRules({
setChQueries(chq);
setAlertDef(initialValue);
}, [initialValue]);
}, [initialValue, initQueryBuilderData]);
// this useEffect updates staging query when
// any of its sub-parameters changes
@ -159,16 +126,15 @@ function FormAlertRules({
// prepare staged query
const sq: StagedQuery = prepareStagedQuery(
queryCategory,
metricQueries,
formulaQueries,
queryBuilderData.queryData,
queryBuilderData.queryFormulas,
promQueries,
chQueries,
);
setStagedQuery(sq);
}, [queryCategory, chQueries, metricQueries, formulaQueries, promQueries]);
}, [queryCategory, chQueries, queryBuilderData, promQueries]);
const onRunQuery = (): void => {
setRunQueryId(Math.random().toString(36).substring(2, 15));
setManualStagedQuery(stagedQuery);
};
@ -190,6 +156,15 @@ function FormAlertRules({
evalWindow: defaultEvalWindow,
});
}
const sq: StagedQuery = prepareStagedQuery(
val,
queryBuilderData.queryData,
queryBuilderData.queryFormulas,
promQueries,
chQueries,
);
setManualStagedQuery(sq);
};
const { notifications } = useNotifications();
@ -244,10 +219,9 @@ function FormAlertRules({
}, [t, chQueries, queryCategory, notifications]);
const validateQBParams = useCallback((): boolean => {
let retval = true;
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
if (!metricQueries || Object.keys(metricQueries).length === 0) {
if (!queryBuilderData.queryData || queryBuilderData.queryData.length === 0) {
notifications.error({
message: 'Error',
description: t('condition_required'),
@ -263,27 +237,8 @@ function FormAlertRules({
return false;
}
Object.keys(metricQueries).forEach((key) => {
if (metricQueries[key].metricName === '') {
notifications.error({
message: 'Error',
description: t('metricname_missing', { where: metricQueries[key].name }),
});
retval = false;
}
});
Object.keys(formulaQueries).forEach((key) => {
if (formulaQueries[key].expression === '') {
notifications.error({
message: 'Error',
description: t('expression_missing', formulaQueries[key].name),
});
retval = false;
}
});
return retval;
}, [t, alertDef, queryCategory, metricQueries, formulaQueries, notifications]);
return true;
}, [t, alertDef, queryCategory, queryBuilderData, notifications]);
const isFormValid = useCallback((): boolean => {
if (!alertDef.alert || alertDef.alert === '') {
@ -321,11 +276,12 @@ function FormAlertRules({
queryCategory === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
condition: {
...alertDef.condition,
compositeMetricQuery: {
builderQueries: prepareBuilderQueries(metricQueries, formulaQueries),
compositeQuery: {
builderQueries: mapQueryDataToApi(queryBuilderData).data,
promQueries,
chQueries,
queryType: queryCategory,
panelType: initQuery.panelType,
},
},
};
@ -335,11 +291,11 @@ function FormAlertRules({
const memoizedPreparePostData = useCallback(preparePostData, [
queryCategory,
alertDef,
metricQueries,
formulaQueries,
queryBuilderData,
promQueries,
chQueries,
alertType,
initQuery,
]);
const saveRule = useCallback(async () => {
@ -458,7 +414,7 @@ function FormAlertRules({
headline={<PlotTag queryType={queryCategory} />}
name=""
threshold={alertDef.condition?.target}
query={debouncedStagedQuery}
query={manualStagedQuery}
selectedInterval={toChartInterval(alertDef.evalWindow)}
/>
);
@ -468,7 +424,7 @@ function FormAlertRules({
headline={<PlotTag queryType={queryCategory} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={debouncedStagedQuery}
query={manualStagedQuery}
/>
);
@ -478,7 +434,6 @@ function FormAlertRules({
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
userQueryKey={runQueryId}
selectedInterval={toChartInterval(alertDef.evalWindow)}
/>
);
@ -498,10 +453,6 @@ function FormAlertRules({
<QuerySection
queryCategory={queryCategory}
setQueryCategory={onQueryCategoryChange}
metricQueries={metricQueries}
setMetricQueries={setMetricQueries}
formulaQueries={formulaQueries}
setFormulaQueries={setFormulaQueries}
promQueries={promQueries}
setPromQueries={setPromQueries}
chQueries={chQueries}

View File

@ -82,7 +82,6 @@ export const InputSmall = styled(Input)`
`;
export const FormContainer = styled(Card)`
padding: 2em;
margin-top: 1rem;
display: flex;
flex-direction: column;

View File

@ -1,105 +1,27 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck
import { Time } from 'container/TopNav/DateTimeSelection/config';
import {
IBuilderQueries,
IChQueries,
IChQuery,
IFormulaQueries,
IFormulaQuery,
IMetricQueries,
IMetricQuery,
IPromQueries,
IPromQuery,
} from 'types/api/alerts/compositeQuery';
import { Query as IStagedQuery } from 'types/api/dashboard/getAll';
import {
IMetricsBuilderQuery,
Query as IStagedQuery,
} from 'types/api/dashboard/getAll';
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
export const toFormulaQueries = (b: IBuilderQueries): IFormulaQueries => {
const f: IFormulaQueries = {};
if (!b) return f;
Object.keys(b).forEach((key) => {
if (key === 'F1') {
f[key] = b[key] as IFormulaQuery;
}
});
return f;
};
export const toMetricQueries = (b: IBuilderQueries): IMetricQueries => {
const m: IMetricQueries = {};
if (!b) return m;
Object.keys(b).forEach((key) => {
if (key !== 'F1') {
m[key] = b[key] as IMetricQuery;
}
});
return m;
};
export const toIMetricsBuilderQuery = (
q: IMetricQuery,
): IMetricsBuilderQuery => ({
name: q.name,
metricName: q.metricName,
tagFilters: q.tagFilters,
groupBy: q.groupBy,
aggregateOperator: q.aggregateOperator,
disabled: q.disabled,
legend: q.legend,
});
export const prepareBuilderQueries = (
m: IMetricQueries,
f: IFormulaQueries,
): IBuilderQueries => {
if (!m) return {};
const b: IBuilderQueries = {
...m,
};
Object.keys(f).forEach((key) => {
b[key] = {
...f[key],
aggregateOperator: undefined,
metricName: '',
};
});
return b;
};
export const prepareStagedQuery = (
t: EQueryType,
m: IMetricQueries,
f: IFormulaQueries,
m: IBuilderQuery[],
f: IBuilderFormula[],
p: IPromQueries,
c: IChQueries,
): IStagedQuery => {
const qbList: IMetricQuery[] = [];
const formulaList: IFormulaQuery[] = [];
const promList: IPromQuery[] = [];
const chQueryList: IChQuery[] = [];
// convert map[string]IMetricQuery to IMetricQuery[]
if (m) {
Object.keys(m).forEach((key) => {
qbList.push(m[key]);
});
}
// convert map[string]IFormulaQuery to IFormulaQuery[]
if (f) {
Object.keys(f).forEach((key) => {
formulaList.push(f[key]);
});
}
// convert map[string]IPromQuery to IPromQuery[]
if (p) {
Object.keys(p).forEach((key) => {
@ -115,13 +37,12 @@ export const prepareStagedQuery = (
return {
queryType: t,
promQL: promList,
// TODO: change it later to actual builder
metricsBuilder: {
formulas: formulaList,
queryBuilder: qbList,
promql: promList,
builder: {
queryFormulas: f,
queryData: m,
},
clickHouse: chQueryList,
clickhouse_sql: chQueryList,
};
};

View File

@ -42,13 +42,13 @@ export const UpdateDashboard = async (
panelTypes: graphType,
query: {
queryType: EQueryType.QUERY_BUILDER,
promQL: [
promql: [
{
name: GetQueryName([]) || '',
...PromQLQueryTemplate,
},
],
clickHouse: [
clickhouse_sql: [
{
name: GetQueryName([]) || '',
...ClickHouseQueryTemplate,

View File

@ -1,8 +1,6 @@
import {
IMetricsBuilderFormula,
IMetricsBuilderQuery,
IQueryBuilderTagFilterItems,
} from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder';
import {
getQueryBuilderQueries,
@ -13,16 +11,25 @@ export const databaseCallsRPS = ({
servicename,
legend,
tagFilterItems,
}: DatabaseCallsRPSProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => {
const metricName = 'signoz_db_latency_count';
const groupBy = ['db_system'];
const itemsA = [
}: DatabaseCallsRPSProps): QueryBuilderData => {
const metricName: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_db_latency_count',
type: null,
};
const groupBy: BaseAutocompleteData[] = [
{ dataType: 'string', isColumn: false, key: 'db_system', type: 'tag' },
];
const itemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
@ -40,20 +47,32 @@ export const databaseCallsRPS = ({
export const databaseCallsAvgDuration = ({
servicename,
tagFilterItems,
}: DatabaseCallProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => {
const metricNameA = 'signoz_db_latency_sum';
const metricNameB = 'signoz_db_latency_count';
}: DatabaseCallProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_db_latency_sum',
type: null,
};
const metricNameB: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_db_latency_count',
type: null,
};
const expression = 'A/B';
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
const additionalItemsA = [
const additionalItemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
@ -79,5 +98,5 @@ interface DatabaseCallsRPSProps extends DatabaseCallProps {
interface DatabaseCallProps {
servicename: string | undefined;
tagFilterItems: IQueryBuilderTagFilterItems[] | [];
tagFilterItems: TagFilterItem[];
}

View File

@ -1,45 +1,67 @@
import {
IMetricsBuilderFormula,
IMetricsBuilderQuery,
IQueryBuilderTagFilterItems,
} from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder';
import {
getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula,
} from './MetricsPageQueriesFactory';
const groupBy = ['address'];
const groupBy: BaseAutocompleteData[] = [
{ dataType: 'string', isColumn: false, key: 'address', type: 'tag' },
];
export const externalCallErrorPercent = ({
servicename,
legend,
tagFilterItems,
}: ExternalCallDurationByAddressProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => {
const metricNameA = 'signoz_external_call_latency_count';
const metricNameB = 'signoz_external_call_latency_count';
const additionalItemsA = [
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_count',
type: null,
};
const metricNameB: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_count',
type: null,
};
const additionalItemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
key: 'status_code',
key: {
dataType: 'int64',
isColumn: false,
key: 'status_code',
type: 'tag',
},
op: 'IN',
value: ['STATUS_CODE_ERROR'],
},
...tagFilterItems,
];
const additionalItemsB = [
const additionalItemsB: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
@ -64,20 +86,32 @@ export const externalCallErrorPercent = ({
export const externalCallDuration = ({
servicename,
tagFilterItems,
}: ExternalCallProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => {
const metricNameA = 'signoz_external_call_latency_sum';
const metricNameB = 'signoz_external_call_latency_count';
}: ExternalCallProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_sum',
type: null,
};
const metricNameB: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_count',
type: null,
};
const expression = 'A/B';
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
const additionalItemsA = [
const additionalItemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
@ -101,15 +135,22 @@ export const externalCallRpsByAddress = ({
servicename,
legend,
tagFilterItems,
}: ExternalCallDurationByAddressProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => {
const metricName = 'signoz_external_call_latency_count';
const itemsA = [
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
const metricName: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_count',
type: null,
};
const itemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
@ -127,19 +168,31 @@ export const externalCallDurationByAddress = ({
servicename,
legend,
tagFilterItems,
}: ExternalCallDurationByAddressProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => {
const metricNameA = 'signoz_external_call_latency_sum';
const metricNameB = 'signoz_external_call_latency_count';
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_sum',
type: null,
};
const metricNameB: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_external_call_latency_count',
type: null,
};
const expression = 'A/B';
const legendFormula = legend;
const disabled = true;
const additionalItemsA = [
const additionalItemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
@ -166,5 +219,5 @@ interface ExternalCallDurationByAddressProps extends ExternalCallProps {
export interface ExternalCallProps {
servicename: string | undefined;
tagFilterItems: IQueryBuilderTagFilterItems[];
tagFilterItems: TagFilterItem[];
}

View File

@ -1,28 +1,30 @@
import {
IMetricsBuilderFormula,
IMetricsBuilderQuery,
IQueryBuilderTagFilterItems,
} from 'types/api/dashboard/getAll';
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
} from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import {
MetricAggregateOperator,
QueryBuilderData,
} from 'types/common/queryBuilder';
export const getQueryBuilderQueries = ({
metricName,
groupBy,
groupBy = [],
legend,
itemsA,
}: BuilderQueriesProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => ({
formulas: [],
queryBuilder: [
}: BuilderQueriesProps): QueryBuilderData => ({
queryFormulas: [],
queryData: [
{
aggregateOperator: 18,
...initialQueryBuilderFormValues,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled: false,
groupBy,
aggregateAttribute: metricName,
legend,
metricName,
name: 'A',
reduceTo: 1,
reduceTo: 'sum',
tagFilters: {
items: itemsA,
op: 'AND',
@ -37,44 +39,42 @@ export const getQueryBuilderQuerieswithFormula = ({
additionalItemsA,
additionalItemsB,
legend,
groupBy,
groupBy = [],
disabled,
expression,
legendFormula,
}: BuilderQuerieswithFormulaProps): {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
} => ({
formulas: [
}: BuilderQuerieswithFormulaProps): QueryBuilderData => ({
queryFormulas: [
{
disabled: false,
...initialFormulaBuilderFormValues,
expression,
name: 'F1',
legend: legendFormula,
},
],
queryBuilder: [
queryData: [
{
aggregateOperator: 18,
...initialQueryBuilderFormValues,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
legend,
metricName: metricNameA,
name: 'A',
reduceTo: 1,
aggregateAttribute: metricNameA,
reduceTo: 'sum',
tagFilters: {
items: additionalItemsA,
op: 'AND',
},
},
{
aggregateOperator: 18,
...initialQueryBuilderFormValues,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
legend,
metricName: metricNameB,
name: 'B',
reduceTo: 1,
aggregateAttribute: metricNameB,
queryName: 'B',
expression: 'B',
reduceTo: 'sum',
tagFilters: {
items: additionalItemsB,
op: 'AND',
@ -84,20 +84,20 @@ export const getQueryBuilderQuerieswithFormula = ({
});
interface BuilderQueriesProps {
metricName: string;
groupBy?: string[];
metricName: BaseAutocompleteData;
groupBy?: BaseAutocompleteData[];
legend: string;
itemsA: IQueryBuilderTagFilterItems[];
itemsA: TagFilterItem[];
}
interface BuilderQuerieswithFormulaProps {
metricNameA: string;
metricNameB: string;
metricNameA: BaseAutocompleteData;
metricNameB: BaseAutocompleteData;
legend: string;
disabled: boolean;
groupBy?: string[];
groupBy?: BaseAutocompleteData[];
expression: string;
legendFormula: string;
additionalItemsA: IQueryBuilderTagFilterItems[];
additionalItemsB: IQueryBuilderTagFilterItems[];
additionalItemsA: TagFilterItem[];
additionalItemsB: TagFilterItem[];
}

View File

@ -1,8 +1,6 @@
import {
IMetricsBuilderFormula,
IMetricsBuilderQuery,
IQueryBuilderTagFilterItems,
} from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder';
import {
getQueryBuilderQueries,
@ -13,19 +11,35 @@ export const operationPerSec = ({
servicename,
tagFilterItems,
topLevelOperations,
}: OperationPerSecProps): IOverviewQueries => {
const metricName = 'signoz_latency_count';
}: OperationPerSecProps): QueryBuilderData => {
const metricName: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_latency_count',
type: null,
};
const legend = 'Operations';
const itemsA = [
const itemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
key: 'operation',
key: {
dataType: 'string',
isColumn: false,
key: 'operation',
type: 'tag',
},
op: 'IN',
value: topLevelOperations,
},
@ -43,41 +57,76 @@ export const errorPercentage = ({
servicename,
tagFilterItems,
topLevelOperations,
}: OperationPerSecProps): IOverviewQueries => {
const metricNameA = 'signoz_calls_total';
const metricNameB = 'signoz_calls_total';
const additionalItemsA = [
}: OperationPerSecProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_calls_total',
type: null,
};
const metricNameB: BaseAutocompleteData = {
dataType: 'float64',
isColumn: true,
key: 'signoz_calls_total',
type: null,
};
const additionalItemsA: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
key: 'operation',
key: {
dataType: 'string',
isColumn: false,
key: 'operation',
type: 'tag',
},
op: 'IN',
value: topLevelOperations,
},
{
id: '',
key: 'status_code',
key: {
dataType: 'int64',
isColumn: false,
key: 'status_code',
type: 'tag',
},
op: 'IN',
value: ['STATUS_CODE_ERROR'],
},
...tagFilterItems,
];
const additionalItemsB = [
const additionalItemsB: TagFilterItem[] = [
{
id: '',
key: 'service_name',
key: {
dataType: 'string',
isColumn: false,
key: 'service_name',
type: 'resource',
},
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
key: 'operation',
key: {
dataType: 'string',
isColumn: false,
key: 'operation',
type: 'tag',
},
op: 'IN',
value: topLevelOperations,
},
@ -102,11 +151,6 @@ export const errorPercentage = ({
export interface OperationPerSecProps {
servicename: string | undefined;
tagFilterItems: IQueryBuilderTagFilterItems[];
tagFilterItems: TagFilterItem[];
topLevelOperations: string[];
}
interface IOverviewQueries {
formulas: IMetricsBuilderFormula[];
queryBuilder: IMetricsBuilderQuery[];
}

View File

@ -1,5 +1,3 @@
/* eslint-disable */
// @ts-nocheck
import { Col } from 'antd';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
import {
@ -14,6 +12,8 @@ import {
import React, { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
@ -29,7 +29,7 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { queries } = useResourceAttribute();
const tagFilterItems = useMemo(
const tagFilterItems: TagFilterItem[] = useMemo(
() =>
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
[queries],
@ -48,29 +48,27 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
const databaseCallsRPSWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: databaseCallsRPS({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: databaseCallsRPS({
servicename,
legend,
tagFilterItems,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
const databaseCallsAverageDurationWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: databaseCallsAvgDuration({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: databaseCallsAvgDuration({
servicename,
tagFilterItems,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);

View File

@ -1,5 +1,3 @@
/* eslint-disable */
// @ts-nocheck
import { Col } from 'antd';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
import {
@ -16,6 +14,7 @@ import {
import React, { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { legend } from './constant';
@ -41,15 +40,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallErrorWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: externalCallErrorPercent({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallErrorPercent({
servicename,
legend: legend.address,
tagFilterItems,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -62,14 +60,13 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallDurationWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: externalCallDuration({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallDuration({
servicename,
tagFilterItems,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -77,15 +74,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallRPSWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: externalCallRpsByAddress({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallRpsByAddress({
servicename,
legend: legend.address,
tagFilterItems,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -93,15 +89,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallDurationAddressWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: externalCallDurationByAddress({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallDurationByAddress({
servicename,
legend: legend.address,
tagFilterItems,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);

View File

@ -1,5 +1,3 @@
/* eslint-disable */
// @ts-nocheck
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import Graph from 'components/Graph';
import { QueryParams } from 'constants/query';
@ -21,6 +19,7 @@ import { useLocation, useParams } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import MetricReducer from 'types/reducer/metrics';
import {
@ -84,15 +83,14 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const operationPerSecWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: operationPerSec({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: operationPerSec({
servicename,
tagFilterItems,
topLevelOperations,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, topLevelOperations, tagFilterItems],
);
@ -100,15 +98,14 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const errorPercentageWidget = useMemo(
() =>
getWidgetQueryBuilder({
queryType: 1,
promQL: [],
// TODO: change it later to actual builder
metricsBuilder: errorPercentage({
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: errorPercentage({
servicename,
tagFilterItems,
topLevelOperations,
}),
clickHouse: [],
clickhouse_sql: [],
}),
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
);

View File

@ -4,7 +4,7 @@ import ROUTES from 'constants/routes';
import { routeConfig } from 'container/SideNav/config';
import { getQueryString } from 'container/SideNav/helper';
import history from 'lib/history';
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { Tags } from 'types/reducer/trace';
export const dbSystemTags: Tags[] = [
@ -90,9 +90,7 @@ export function onGraphClickHandler(
};
}
export const handleNonInQueryRange = (
tags: IQueryBuilderTagFilterItems[],
): IQueryBuilderTagFilterItems[] =>
export const handleNonInQueryRange = (tags: TagFilterItem[]): TagFilterItem[] =>
tags.map((tag) => {
if (tag.op === 'Not IN') {
return {

View File

@ -13,7 +13,7 @@ import { IClickHouseQueryHandleChange } from './types';
interface IClickHouseQueryContainerProps {
queryData: Query;
updateQueryData: (args: IHandleUpdatedQuery) => void;
clickHouseQueries: Query['clickHouse'];
clickHouseQueries: Query['clickhouse_sql'];
}
function ClickHouseQueryContainer({
queryData,

View File

@ -69,8 +69,6 @@ function MetricsBuilder({
});
}, [metricName]);
// TODO: rewrite to Form component from antd
return (
<QueryHeader
name={queryData.name}

View File

@ -1,21 +1,12 @@
/* eslint-disable */
// TODO: fix it after merge actual functionality
// @ts-nocheck
import { Query } from 'types/api/dashboard/getAll';
import {
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME,
WIDGET_QUERY_BUILDER_QUERY_KEY_NAME,
} from '../../constants';
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].queryBuilder;
const formulas =
query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
];
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,21 +1,10 @@
/* eslint-disable */
// @ts-ignore
// @ts-nocheck
import { EQueryType } from 'types/common/dashboard';
import { EQueryTypeToQueryKeyMapping } from './types';
export const WIDGET_PROMQL_QUERY_KEY_NAME = EQueryType.PROM;
export const WIDGET_PROMQL_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.PROM =
EQueryTypeToQueryKeyMapping[EQueryType[EQueryType.PROM]];
export const WIDGET_CLICKHOUSE_QUERY_KEY_NAME = EQueryType.CLICKHOUSE;
export const WIDGET_CLICKHOUSE_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.CLICKHOUSE = EQueryTypeToQueryKeyMapping[
EQueryType[EQueryType.CLICKHOUSE]
] as string;
export const WIDGET_QUERY_BUILDER_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.QUERY_BUILDER = EQueryTypeToQueryKeyMapping[
EQueryType[EQueryType.QUERY_BUILDER]
] as string;
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

@ -30,7 +30,6 @@ import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
import TabHeader from './TabHeader';
import { IHandleUpdatedQuery } from './types';
import { getQueryKey } from './utils/getQueryKey';
import { showUnstagedStashConfirmBox } from './utils/userSettings';
function QuerySection({
@ -76,15 +75,10 @@ function QuerySection({
queryA: Query,
queryB: Query,
queryCategory: EQueryType,
): boolean => {
const keyOfConcern = getQueryKey(queryCategory);
return !isEqual(queryA[keyOfConcern], queryB[keyOfConcern]);
};
): boolean => !isEqual(queryA[queryCategory], queryB[queryCategory]);
useEffect(() => {
handleUnstagedChanges(
queryDiff(query, localQueryChanges, parseInt(`${queryCategory}`, 10)),
);
handleUnstagedChanges(queryDiff(query, localQueryChanges, queryCategory));
}, [handleUnstagedChanges, localQueryChanges, query, queryCategory]);
const regenRctKeys = (): void => {
@ -111,11 +105,7 @@ function QuerySection({
const handleQueryCategoryChange = (qCategory: string): void => {
// If true, then it means that the user has made some changes and haven't staged them
const unstagedChanges = queryDiff(
query,
localQueryChanges,
parseInt(`${queryCategory}`, 10),
);
const unstagedChanges = queryDiff(query, localQueryChanges, queryCategory);
if (unstagedChanges && showUnstagedStashConfirmBox()) {
// eslint-disable-next-line no-alert
@ -125,10 +115,10 @@ function QuerySection({
return;
}
setQueryCategory(parseInt(`${qCategory}`, 10));
setQueryCategory(qCategory as EQueryType);
const newLocalQuery = {
...cloneDeep(query),
queryType: parseInt(`${qCategory}`, 10),
queryType: qCategory as EQueryType,
};
setLocalQueryChanges(newLocalQuery);
regenRctKeys();
@ -147,7 +137,7 @@ function QuerySection({
const items = [
{
key: EQueryType.QUERY_BUILDER.toString(),
key: EQueryType.QUERY_BUILDER,
label: 'Query Builder',
tab: (
<TabHeader
@ -162,7 +152,7 @@ function QuerySection({
children: <QueryBuilder panelType={selectedGraph} />,
},
{
key: EQueryType.CLICKHOUSE.toString(),
key: EQueryType.CLICKHOUSE,
label: 'ClickHouse Query',
tab: (
<TabHeader
@ -186,7 +176,7 @@ function QuerySection({
),
},
{
key: EQueryType.PROM.toString(),
key: EQueryType.PROM,
label: 'PromQL',
tab: (
<TabHeader
@ -213,8 +203,8 @@ function QuerySection({
<Tabs
type="card"
style={{ width: '100%' }}
defaultActiveKey={queryCategory.toString()}
activeKey={queryCategory.toString()}
defaultActiveKey={queryCategory}
activeKey={queryCategory}
onChange={handleQueryCategoryChange}
tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>

View File

@ -1,19 +1,5 @@
import { Query } from 'types/api/dashboard/getAll';
export type TQueryCategories = 'query_builder' | 'clickhouse_query' | 'promql';
export enum EQueryCategories {
query_builder = 0,
clickhouse_query,
promql,
}
export enum EQueryTypeToQueryKeyMapping {
QUERY_BUILDER = 'builder',
CLICKHOUSE = 'clickHouse',
PROM = 'promQL',
}
export interface IHandleUpdatedQuery {
updatedQuery: Query;
}

View File

@ -1,10 +0,0 @@
import { EQueryType } from 'types/common/dashboard';
import { EQueryTypeToQueryKeyMapping } from '../types';
export const getQueryKey = (
queryCategory: EQueryType,
): EQueryTypeToQueryKeyMapping =>
EQueryTypeToQueryKeyMapping[
EQueryType[queryCategory] as keyof typeof EQueryTypeToQueryKeyMapping
];

View File

@ -19,26 +19,27 @@ export const QueryBuilder = memo(function QueryBuilder({
const {
queryBuilderData,
setupInitialDataSource,
resetQueryBuilderData,
resetQueryBuilderInfo,
addNewQuery,
addNewFormula,
handleSetPanelType,
} = useQueryBuilder();
useEffect(() => {
if (config && config.queryVariant === 'static') {
setupInitialDataSource(config.initialDataSource);
}
return (): void => {
setupInitialDataSource(null);
};
}, [config, setupInitialDataSource]);
useEffect(() => {
handleSetPanelType(panelType);
}, [handleSetPanelType, panelType]);
useEffect(
() => (): void => {
resetQueryBuilderData();
resetQueryBuilderInfo();
},
[resetQueryBuilderData],
[resetQueryBuilderInfo],
);
const isDisabledQueryButton = useMemo(
@ -51,6 +52,13 @@ export const QueryBuilder = memo(function QueryBuilder({
[queryBuilderData],
);
const isAvailableToDisableQuery = useMemo(
() =>
queryBuilderData.queryData.length > 1 ||
queryBuilderData.queryFormulas.length > 0,
[queryBuilderData],
);
return (
<Row gutter={[0, 20]} justify="start">
<Col span={24}>
@ -59,10 +67,9 @@ export const QueryBuilder = memo(function QueryBuilder({
<Col key={query.queryName} span={24}>
<Query
index={index}
isAvailableToDisable={queryBuilderData.queryData.length > 1}
isAvailableToDisable={isAvailableToDisableQuery}
queryVariant={config?.queryVariant || 'dropdown'}
query={query}
panelType={panelType}
/>
</Col>
))}

View File

@ -3,7 +3,7 @@ import styled from 'styled-components';
export const StyledLabel = styled.div`
padding: 0 0.6875rem;
min-height: 2rem;
width: 100%;
min-width: 5.625rem;
display: inline-flex;
white-space: nowrap;
align-items: center;

View File

@ -7,7 +7,6 @@ export const StyledButton = styled(Button)<{ $isAvailableToDisable: boolean }>`
padding: ${(props): string =>
props.$isAvailableToDisable ? '0.43rem' : '0.43rem 0.68rem'};
border-radius: 0.375rem;
margin-right: 0.5rem;
pointer-events: ${(props): string =>
props.$isAvailableToDisable ? 'default' : 'none'};
`;

View File

@ -1,4 +1,3 @@
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export type QueryProps = {
@ -6,5 +5,4 @@ export type QueryProps = {
isAvailableToDisable: boolean;
query: IBuilderQuery;
queryVariant: 'static' | 'dropdown';
panelType: ITEMS;
};

View File

@ -20,6 +20,7 @@ import AggregateEveryFilter from 'container/QueryBuilder/filters/AggregateEveryF
import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter';
import { OrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryOperations';
// ** Hooks
import React, { ChangeEvent, memo, ReactNode, useCallback } from 'react';
@ -34,8 +35,8 @@ export const Query = memo(function Query({
isAvailableToDisable,
queryVariant,
query,
panelType,
}: QueryProps): JSX.Element {
const { panelType } = useQueryBuilder();
const {
operators,
isMetricsDataSource,
@ -45,7 +46,7 @@ export const Query = memo(function Query({
handleChangeQueryData,
handleChangeOperator,
handleDeleteQuery,
} = useQueryOperations({ index, query, panelType });
} = useQueryOperations({ index, query });
const handleChangeAggregateEvery = useCallback(
(value: IBuilderQuery['stepInterval']) => {
@ -211,7 +212,7 @@ export const Query = memo(function Query({
return (
<ListItemWrapper onDelete={handleDeleteQuery}>
<Col span={24}>
<Row align="middle">
<Row align="middle" gutter={[5, 11]}>
<Col>
<ListMarker
isDisabled={query.disabled}
@ -220,17 +221,19 @@ export const Query = memo(function Query({
index={index}
isAvailableToDisable={isAvailableToDisable}
/>
</Col>
<Col>
{queryVariant === 'dropdown' ? (
<DataSourceDropdown
onChange={handleChangeDataSource}
value={query.dataSource}
style={{ marginRight: '0.5rem', minWidth: '5.625rem' }}
style={{ minWidth: '5.625rem' }}
/>
) : (
<FilterLabel label={transformToUpperCase(query.dataSource)} />
)}
</Col>
<Col flex="1">
<Col flex="1 1 20rem">
<Row gutter={[11, 5]}>
{isMetricsDataSource && (
<Col>

View File

@ -3,11 +3,12 @@ import { AutoComplete, Spin } from 'antd';
// ** Api
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
import { initialAggregateAttribute } from 'constants/queryBuilder';
import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue';
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
import React, { memo, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
import { ExtendedSelectOption } from 'types/common/select';
import { transformToUpperCase } from 'utils/transformToUpperCase';
import { selectStyle } from '../QueryBuilderSearch/config';
@ -36,23 +37,35 @@ export const AggregatorFilter = memo(function AggregatorFilter({
{ enabled: !!query.aggregateOperator && !!query.dataSource },
);
const handleSearchAttribute = (searchText: string): void =>
setSearchText(searchText);
const handleSearchAttribute = (searchText: string): void => {
const { key } = getFilterObjectValue(searchText);
setSearchText(key);
};
const optionsData: SelectOption<string, string>[] =
const optionsData: ExtendedSelectOption[] =
data?.payload?.attributeKeys?.map((item) => ({
label: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
value: item.key,
value: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
key: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
})) || [];
const handleChangeAttribute = (value: string): void => {
const { key, isColumn } = getFilterObjectValue(value);
const currentAttributeObj = data?.payload?.attributeKeys?.find(
(item) => item.key === value,
) || { ...initialAggregateAttribute, key: value };
(item) => item.key === key && isColumn === item.isColumn,
) || { ...initialAggregateAttribute, key };
setSearchText('');
onChange(currentAttributeObj);

View File

@ -6,11 +6,3 @@ export type GroupByFilterProps = {
onChange: (values: BaseAutocompleteData[]) => void;
disabled: boolean;
};
export type GroupByFilterValue = {
disabled: boolean | undefined;
key: string;
label: string;
title: string | undefined;
value: string;
};

View File

@ -2,19 +2,17 @@ import { Select, Spin } from 'antd';
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
// ** Constants
import { QueryBuilderKeys } from 'constants/queryBuilder';
import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue';
// ** Components
// ** Helpers
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
import React, { memo, useState } from 'react';
import React, { memo, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { SelectOption } from 'types/common/select';
import { ExtendedSelectOption } from 'types/common/select';
import { selectStyle } from '../QueryBuilderSearch/config';
import {
GroupByFilterProps,
GroupByFilterValue,
} from './GroupByFilter.interfaces';
import { GroupByFilterProps } from './GroupByFilter.interfaces';
export const GroupByFilter = memo(function GroupByFilter({
query,
@ -48,28 +46,44 @@ export const GroupByFilter = memo(function GroupByFilter({
setIsFocused(true);
};
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 optionsData: ExtendedSelectOption[] = useMemo(() => {
if (data && data.payload && data.payload.attributeKeys) {
return data.payload.attributeKeys.map((item) => ({
label: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
value: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
key: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
}));
}
const handleChange = (values: GroupByFilterValue[]): void => {
return [];
}, [data]);
const handleChange = (values: ExtendedSelectOption[]): void => {
const groupByValues: BaseAutocompleteData[] = values.map((item) => {
const responseKeys = data?.payload?.attributeKeys || [];
const { key, isColumn } = getFilterObjectValue(item.value);
const existGroupResponse = responseKeys.find(
(group) => group.key === item.value,
(group) => group.key === key && group.isColumn === isColumn,
);
if (existGroupResponse) {
return existGroupResponse;
}
const existGroupQuery = query.groupBy.find(
(group) => group.key === item.value,
(group) => group.key === key && group.isColumn === isColumn,
);
if (existGroupQuery) {
@ -78,7 +92,7 @@ export const GroupByFilter = memo(function GroupByFilter({
return {
isColumn: null,
key: item.value,
key,
dataType: null,
type: null,
};
@ -88,16 +102,22 @@ export const GroupByFilter = memo(function GroupByFilter({
onChange(groupByValues);
};
const values: GroupByFilterValue[] = query.groupBy.map((item) => ({
const values: ExtendedSelectOption[] = 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,
key: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
value: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
}));
return (
@ -110,8 +130,8 @@ export const GroupByFilter = memo(function GroupByFilter({
showArrow={false}
onBlur={onBlur}
onFocus={onFocus}
filterOption={false}
options={optionsData}
filterOption={false}
labelInValue
value={values}
notFoundContent={isFetching ? <Spin size="small" /> : null}

View File

@ -1,6 +1,7 @@
import { Select } from 'antd';
// ** Constants
import { HAVING_OPERATORS, initialHavingValues } from 'constants/queryBuilder';
import { HAVING_FILTER_REGEXP } from 'constants/regExp';
import { HavingFilterTag } from 'container/QueryBuilder/components';
import { HavingTagRenderProps } from 'container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.interfaces';
// ** Hooks
@ -101,9 +102,7 @@ export function HavingFilter({
const values = getHavingObject(search).value.join(' ');
if (values) {
const numRegexp = /^[-\d.,\s]+$/;
return numRegexp.test(values);
return HAVING_FILTER_REGEXP.test(values);
}
return true;

View File

@ -75,10 +75,13 @@ function QueryBuilderSearch({
useEffect(() => {
const initialTagFilters: TagFilter = { items: [], op: 'AND' };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
initialTagFilters.items = tags.map((tag) => {
const [tagKey, tagOperator, ...tagValue] = tag.split(' ');
return {
id: uuid().slice(0, 8),
// TODO: key should be fixed by Chintan Sudani
key: tagKey,
op: tagOperator,
value: tagValue.map((i) => i.replace(',', '')),

View File

@ -2,11 +2,9 @@ import {
initialAggregateAttribute,
initialQueryBuilderFormValues,
mapOfFilters,
mapOfOperators,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
@ -15,14 +13,14 @@ import {
HandleChangeQueryData,
UseQueryOperations,
} from 'types/common/operations.types';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
export const useQueryOperations: UseQueryOperations = ({
query,
index,
panelType,
}) => {
const { handleSetQueryData, removeEntityByIndex } = useQueryBuilder();
export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
const {
handleSetQueryData,
removeEntityByIndex,
panelType,
} = useQueryBuilder();
const [operators, setOperators] = useState<string[]>([]);
const [listOfAdditionalFilters, setListOfAdditionalFilters] = useState<
string[]
@ -57,24 +55,6 @@ export const useQueryOperations: UseQueryOperations = ({
[index, query, handleSetQueryData],
);
const getNewOperators = useCallback(
(dataSource: DataSource, currentPanelType: ITEMS): string[] => {
let operatorsByDataSource = mapOfOperators[dataSource];
if (
dataSource !== DataSource.METRICS &&
currentPanelType !== PANEL_TYPES.LIST
) {
operatorsByDataSource = operatorsByDataSource.filter(
(operator) => operator !== StringOperators.NOOP,
);
}
return operatorsByDataSource;
},
[],
);
const getNewListOfAdditionalFilters = useCallback(
(dataSource: DataSource): string[] =>
mapOfFilters[dataSource].map((item) => item.text),
@ -96,7 +76,10 @@ export const useQueryOperations: UseQueryOperations = ({
const handleChangeDataSource = useCallback(
(nextSource: DataSource): void => {
const newOperators = getNewOperators(nextSource, panelType);
const newOperators = getOperatorsBySourceAndPanelType({
dataSource: nextSource,
panelType,
});
const entries = Object.entries(initialQueryBuilderFormValues).filter(
([key]) => key !== 'queryName' && key !== 'expression',
@ -114,7 +97,7 @@ export const useQueryOperations: UseQueryOperations = ({
setOperators(newOperators);
handleSetQueryData(index, newQuery);
},
[index, query, panelType, handleSetQueryData, getNewOperators],
[index, query, panelType, handleSetQueryData],
);
const handleDeleteQuery = useCallback(() => {
@ -139,11 +122,12 @@ export const useQueryOperations: UseQueryOperations = ({
);
useEffect(() => {
if (operators.length === 0) {
const initialOperators = getNewOperators(dataSource, panelType);
setOperators(initialOperators);
}
}, [operators, dataSource, panelType, getNewOperators]);
const initialOperators = getOperatorsBySourceAndPanelType({
dataSource,
panelType,
});
setOperators(initialOperators);
}, [dataSource, panelType]);
useEffect(() => {
const additionalFilters = getNewListOfAdditionalFilters(dataSource);

View File

@ -11,7 +11,7 @@ import {
} from 'hooks/useResourceAttribute/types';
import { decode } from 'js-base64';
import history from 'lib/history';
import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { OperatorValues, Tags } from 'types/reducer/trace';
import { v4 as uuid } from 'uuid';
@ -63,10 +63,10 @@ export const convertRawQueriesToTraceSelectedTags = (
/* Convert resource attributes to tagFilter items for queryBuilder */
export const resourceAttributesToTagFilterItems = (
queries: IResourceAttribute[],
): IQueryBuilderTagFilterItems[] =>
): TagFilterItem[] =>
queries.map((res) => ({
id: `${res.id}`,
key: `${res.tagKey}`,
key: { key: res.tagKey, isColumn: false, type: null, dataType: null },
op: `${res.operator}`,
value: `${res.tagValue}`.split(','),
}));

View File

@ -17,7 +17,7 @@ export const convertNewDataToOld = (
QueryData['values']
>((acc, currentInfo) => {
const renderValues: [number, string] = [
currentInfo.timestamp,
currentInfo.timestamp / 1000,
currentInfo.value,
];

View File

@ -0,0 +1,31 @@
import { TYPE_ADDON_REGEXP } from 'constants/regExp';
import {
AutocompleteType,
BaseAutocompleteData,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
const isTypeExist = (str: string): boolean => {
const types: AutocompleteType[] = ['tag', 'resource'];
let isExist = false;
types.forEach((type) => {
if (str.includes(type)) {
isExist = true;
}
});
return isExist;
};
export const getFilterObjectValue = (
value: string,
): Omit<BaseAutocompleteData, 'dataType' | 'type'> => {
const isExist = isTypeExist(value);
if (!isExist) {
return { isColumn: true, key: value };
}
const splittedValue = value.split(TYPE_ADDON_REGEXP);
return { isColumn: false, key: splittedValue[1] };
};

View File

@ -0,0 +1,24 @@
import { mapOfOperators, PANEL_TYPES } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
type GetQueryOperatorsParams = {
dataSource: DataSource;
panelType: GRAPH_TYPES;
};
// Modify this function if need special conditions for filtering of the operators
export const getOperatorsBySourceAndPanelType = ({
dataSource,
panelType,
}: GetQueryOperatorsParams): string[] => {
let operatorsByDataSource = mapOfOperators[dataSource];
if (dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST) {
operatorsByDataSource = operatorsByDataSource.filter(
(operator) => operator !== StringOperators.NOOP,
);
}
return operatorsByDataSource;
};

View File

@ -0,0 +1,20 @@
import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
import { isQuery, QueryBuilderData } from 'types/common/queryBuilder';
import { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
export const mapQueryDataFromApi = (
data: QueryDataResourse,
): QueryBuilderData => {
const queryData: QueryBuilderData['queryData'] = [];
const queryFormulas: QueryBuilderData['queryFormulas'] = [];
Object.entries(data).forEach(([, value]) => {
if (isQuery(value)) {
queryData.push({ ...initialQueryBuilderFormValues, ...value });
} else {
queryFormulas.push(value);
}
});
return { queryData, queryFormulas };
};

View File

@ -1,15 +1,9 @@
import {
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder';
type MapQueryDataToApiResult = {
data: Record<string, IBuilderQuery | IBuilderFormula>;
newLegendMap: Record<string, string>;
};
type MapQuery = Record<string, IBuilderQuery>;
type MapFormula = Record<string, IBuilderFormula>;
import {
MapFormula,
MapQuery,
MapQueryDataToApiResult,
} from 'types/common/queryBuilderMappers.types';
export const mapQueryDataToApi = (
data: QueryBuilderData,
@ -41,6 +35,8 @@ export const mapQueryDataToApi = (
},
};
newLegendMap[formula.queryName] = formula.legend;
return newResult;
},
{},

View File

@ -3,11 +3,13 @@ import {
formulasNames,
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
mapOfOperators,
MAX_FORMULAS,
MAX_QUERIES,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
import React, {
createContext,
PropsWithChildren,
@ -16,7 +18,6 @@ import React, {
useState,
} from 'react';
// ** Types
// TODO: Rename Types on the Reusable type for any source
import {
IBuilderFormula,
IBuilderQuery,
@ -30,9 +31,12 @@ import {
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
queryBuilderData: { queryData: [], queryFormulas: [] },
initialDataSource: null,
panelType: PANEL_TYPES.TIME_SERIES,
resetQueryBuilderData: () => {},
resetQueryBuilderInfo: () => {},
handleSetQueryData: () => {},
handleSetFormulaData: () => {},
handleSetPanelType: () => {},
initQueryBuilderData: () => {},
setupInitialDataSource: () => {},
removeEntityByIndex: () => {},
@ -48,25 +52,28 @@ const initialQueryBuilderData: QueryBuilderData = {
export function QueryBuilderProvider({
children,
}: PropsWithChildren): JSX.Element {
// TODO: this is temporary. It will be used when we have fixed dataSource and need create new query with this data source
// 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 [panelType, setPanelType] = useState<GRAPH_TYPES>(
PANEL_TYPES.TIME_SERIES,
);
const [queryBuilderData, setQueryBuilderData] = useState<QueryBuilderData>({
queryData: [],
queryFormulas: [],
});
// ** Method for resetting query builder data
const resetQueryBuilderData = useCallback((): void => {
const resetQueryBuilderInfo = useCallback((): void => {
setInitialDataSource(null);
setPanelType(PANEL_TYPES.TIME_SERIES);
}, []);
const resetQueryBuilderData = useCallback(() => {
setQueryBuilderData(initialQueryBuilderData);
}, []);
// ** Method for setuping query builder data
// ** Before setuping transform data from backend to frontend format
const initQueryBuilderData = useCallback(
(queryBuilderData: QueryBuilderData): void => {
setQueryBuilderData(queryBuilderData);
@ -101,14 +108,17 @@ export function QueryBuilderProvider({
...(initialDataSource
? {
dataSource: initialDataSource,
aggregateOperator: mapOfOperators[initialDataSource][0],
aggregateOperator: getOperatorsBySourceAndPanelType({
dataSource: initialDataSource,
panelType,
})[0],
}
: {}),
};
return newQuery;
},
[initialDataSource],
[initialDataSource, panelType],
);
const createNewFormula = useCallback((formulas: IBuilderFormula[]) => {
@ -184,7 +194,6 @@ export function QueryBuilderProvider({
[updateQueryBuilderData],
);
const handleSetFormulaData = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(index: number, formulaData: IBuilderFormula): void => {
setQueryBuilderData((prevState) => {
const updatedFormulasBuilderData = updateFormulaBuilderData(
@ -202,13 +211,20 @@ export function QueryBuilderProvider({
[updateFormulaBuilderData],
);
const handleSetPanelType = useCallback((newPanelType: GRAPH_TYPES) => {
setPanelType(newPanelType);
}, []);
const contextValues: QueryBuilderContextType = useMemo(
() => ({
queryBuilderData,
initialDataSource,
panelType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
handleSetPanelType,
initQueryBuilderData,
setupInitialDataSource,
removeEntityByIndex,
@ -218,9 +234,12 @@ export function QueryBuilderProvider({
[
queryBuilderData,
initialDataSource,
panelType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
handleSetPanelType,
initQueryBuilderData,
setupInitialDataSource,
removeEntityByIndex,

View File

@ -60,13 +60,13 @@ export const GetDashboard = ({
},
query: {
queryType: EQueryType.QUERY_BUILDER,
promQL: [
promql: [
{
name: GetQueryName([]) as string,
...PromQLQueryTemplate,
},
],
clickHouse: [
clickhouse_sql: [
{
name: GetQueryName([]) as string,
...ClickHouseQueryTemplate,

View File

@ -6,7 +6,6 @@ import { getMetricsQueryRange } from 'api/metrics/getQueryRange';
import { AxiosError } from 'axios';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { EQueryTypeToQueryKeyMapping } from 'container/NewWidget/LeftContainer/QuerySection/types';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Time } from 'container/TopNav/DateTimeSelection/config';
import GetMaxMinTime from 'lib/getMaxMinTime';
@ -38,24 +37,21 @@ export async function GetMetricQueryRange({
globalSelectedInterval: Time;
variables?: Record<string, unknown>;
}): Promise<SuccessResponse<MetricRangePayloadProps> | ErrorResponse> {
const { queryType } = query;
const queryKey: Record<EQueryTypeToQueryKeyMapping, string> =
EQueryTypeToQueryKeyMapping[EQueryType[query.queryType]];
const queryData = query[queryKey];
const queryData = query[query.queryType];
let legendMap: Record<string, string> = {};
const QueryPayload = {
compositeQuery: {
queryType: queryKey,
queryType: query.queryType,
panelType: graphType,
},
};
switch (queryType as EQueryType) {
switch (query.queryType) {
case EQueryType.QUERY_BUILDER: {
const { queryData, queryFormulas } = query.builder;
const { queryData: data, queryFormulas } = query.builder;
const builderQueries = mapQueryDataToApi({
queryData,
queryData: data,
queryFormulas,
});
legendMap = builderQueries.newLegendMap;

View File

@ -1,6 +1,5 @@
// this list must exactly match with the backend
export enum AlertTypes {
NONE = 'NONE',
METRICS_BASED_ALERT = 'METRIC_BASED_ALERT',
LOGS_BASED_ALERT = 'LOGS_BASED_ALERT',
TRACES_BASED_ALERT = 'TRACES_BASED_ALERT',

View File

@ -1,3 +1,4 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import {
IClickHouseQuery,
IMetricsBuilderFormula,
@ -6,12 +7,14 @@ import {
IQueryBuilderTagFilters,
} from 'types/api/dashboard/getAll';
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
import { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
export interface ICompositeMetricQuery {
builderQueries: IBuilderQueries;
builderQueries: QueryDataResourse;
promQueries: IPromQueries;
chQueries: IChQueries;
queryType: EQueryType;
panelType: GRAPH_TYPES;
}
export interface IChQueries {

View File

@ -24,7 +24,7 @@ export interface AlertDef {
}
export interface RuleCondition {
compositeMetricQuery: ICompositeMetricQuery;
compositeQuery: ICompositeMetricQuery;
op?: string | undefined;
target?: number | undefined;
matchType?: string | undefined;

View File

@ -1,17 +0,0 @@
export type QueryType = 1 | 2 | 3;
export const QUERY_BUILDER: QueryType = 1;
export const PROMQL: QueryType = 3;
export const resolveQueryCategoryName = (s: number): string => {
switch (s) {
case 1:
return 'Query Builder';
case 2:
return 'Clickhouse Query';
case 3:
return 'PromQL';
default:
return '';
}
};

View File

@ -92,9 +92,9 @@ export interface PromQLWidgets extends IBaseWidget {
}
export interface Query {
queryType: EQueryType;
promQL: IPromQLQuery[];
promql: IPromQLQuery[];
builder: QueryBuilderData;
clickHouse: IClickHouseQuery[];
clickhouse_sql: IClickHouseQuery[];
}
export interface IMetricsBuilderFormula {

View File

@ -1,5 +0,0 @@
export enum EQueryTypeToQueryKeyMapping {
QUERY_BUILDER = 'metricsBuilder',
CLICKHOUSE = 'clickHouse',
PROM = 'promQL',
}

View File

@ -2,11 +2,13 @@ export type LocalDataType = 'number' | 'string' | 'bool';
export type DataType = 'int64' | 'float64' | 'string' | 'bool';
export type AutocompleteType = 'tag' | 'resource';
export interface BaseAutocompleteData {
dataType: DataType | null;
isColumn: boolean | null;
key: string;
type: 'tag' | 'resource' | null;
type: AutocompleteType | null;
}
export interface IQueryAutocompleteResponse {

View File

@ -13,14 +13,13 @@ export interface IBuilderFormula {
export interface TagFilterItem {
id: string;
key: string;
// TODO: type it in the future
key?: BaseAutocompleteData;
op: string;
value: string[];
}
export interface TagFilter {
items: TagFilterItem[] | [];
items: TagFilterItem[];
// TODO: type it in the future
op: string;
}

View File

@ -1,7 +1,7 @@
export enum EQueryType {
QUERY_BUILDER = 1,
CLICKHOUSE,
PROM,
QUERY_BUILDER = 'builder',
CLICKHOUSE = 'clickhouse_sql',
PROM = 'promql',
}
export enum EAggregateOperator {

View File

@ -3,10 +3,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
type UseQueryOperationsParams = Pick<
QueryProps,
'index' | 'panelType' | 'query'
>;
type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'>;
export type HandleChangeQueryData = <
Key extends keyof IBuilderQuery,

View File

@ -1,3 +1,4 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import {
IBuilderFormula,
IBuilderQuery,
@ -45,7 +46,6 @@ export enum NumberOperators {
HIST_QUANTILE_99 = 'hist_quantile_99',
}
// TODO: add boolean operators from backend
export enum BoolOperators {
NOOP = 'noop',
COUNT = 'count',
@ -147,13 +147,20 @@ export type QueryBuilderData = {
queryFormulas: IBuilderFormula[];
};
// ** TODO: temporary types for context, fix it during development
export const isQuery = (
query: IBuilderFormula | IBuilderQuery,
): query is IBuilderQuery =>
'dataSource' in query && 'aggregateOperator' in query;
export type QueryBuilderContextType = {
queryBuilderData: QueryBuilderData;
initialDataSource: DataSource | null;
panelType: GRAPH_TYPES;
resetQueryBuilderData: () => void;
resetQueryBuilderInfo: () => void;
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
handleSetPanelType: (newPanelType: GRAPH_TYPES) => void;
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
removeEntityByIndex: (type: keyof QueryBuilderData, index: number) => void;

View File

@ -0,0 +1,14 @@
import {
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
export type MapQuery = Record<string, IBuilderQuery>;
export type MapFormula = Record<string, IBuilderFormula>;
export type QueryDataResourse = Record<string, IBuilderQuery | IBuilderFormula>;
export type MapQueryDataToApiResult = {
data: QueryDataResourse;
newLegendMap: Record<string, string>;
};

View File

@ -2,3 +2,11 @@ export type SelectOption<Value, Label extends unknown = string> = {
value: Value;
label: Label;
};
export type ExtendedSelectOption = {
disabled?: boolean;
key: string;
label: string;
title?: string;
value: string;
};