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

View File

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

View File

@ -1,36 +1,20 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, Tabs } from 'antd'; import { Button, Tabs } from 'antd';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula'; import { QueryBuilder } from 'container/QueryBuilder';
import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query'; import React from 'react';
import {
IQueryBuilderFormulaHandleChange,
IQueryBuilderQueryHandleChange,
} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
import { useNotifications } from 'hooks/useNotifications';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertTypes } from 'types/api/alerts/alertTypes';
import { import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
IChQueries, import { EQueryType } from 'types/common/dashboard';
IFormulaQueries,
IMetricQueries,
IPromQueries,
} from 'types/api/alerts/compositeQuery';
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
import ChQuerySection from './ChQuerySection'; import ChQuerySection from './ChQuerySection';
import PromqlSection from './PromqlSection'; import PromqlSection from './PromqlSection';
import { FormContainer, QueryButton, StepHeading } from './styles'; import { FormContainer, StepHeading } from './styles';
import { toIMetricsBuilderQuery } from './utils';
function QuerySection({ function QuerySection({
queryCategory, queryCategory,
setQueryCategory, setQueryCategory,
metricQueries,
setMetricQueries,
formulaQueries,
setFormulaQueries,
promQueries, promQueries,
setPromQueries, setPromQueries,
chQueries, chQueries,
@ -41,9 +25,9 @@ function QuerySection({
// init namespace for translations // init namespace for translations
const { t } = useTranslation('alerts'); const { t } = useTranslation('alerts');
const handleQueryCategoryChange = (s: string): void => { const handleQueryCategoryChange = (queryType: string): void => {
if ( if (
parseInt(s, 10) === EQueryType.PROM && queryType === EQueryType.PROM &&
(!promQueries || Object.keys(promQueries).length === 0) (!promQueries || Object.keys(promQueries).length === 0)
) { ) {
setPromQueries({ setPromQueries({
@ -58,7 +42,7 @@ function QuerySection({
} }
if ( if (
parseInt(s, 10) === EQueryType.CLICKHOUSE && queryType === EQueryType.CLICKHOUSE &&
(!chQueries || Object.keys(chQueries).length === 0) (!chQueries || Object.keys(chQueries).length === 0)
) { ) {
setChQueries({ 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 => ( const renderPromqlUI = (): JSX.Element => (
<PromqlSection promQueries={promQueries} setPromQueries={setPromQueries} /> <PromqlSection promQueries={promQueries} setPromQueries={setPromQueries} />
); );
@ -221,61 +66,14 @@ function QuerySection({
<ChQuerySection chQueries={chQueries} setChQueries={setChQueries} /> <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 => ( const renderMetricUI = (): JSX.Element => (
<div> <QueryBuilder
{metricQueries && panelType={PANEL_TYPES.TIME_SERIES}
Object.keys(metricQueries).map((key: string) => { config={{
// todo(amol): need to handle this in fetch queryVariant: 'static',
const current = metricQueries[key]; initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType],
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>
); );
const handleRunQuery = (): void => { const handleRunQuery = (): void => {
@ -285,19 +83,18 @@ function QuerySection({
const tabs = [ const tabs = [
{ {
label: t('tab_qb'), label: t('tab_qb'),
key: EQueryType.QUERY_BUILDER.toString(), key: EQueryType.QUERY_BUILDER,
disabled: true,
}, },
{ {
label: t('tab_chquery'), label: t('tab_chquery'),
key: EQueryType.CLICKHOUSE.toString(), key: EQueryType.CLICKHOUSE,
}, },
]; ];
const items = [ const items = [
{ label: t('tab_qb'), key: EQueryType.QUERY_BUILDER.toString() }, { label: t('tab_qb'), key: EQueryType.QUERY_BUILDER },
{ label: t('tab_chquery'), key: EQueryType.CLICKHOUSE.toString() }, { label: t('tab_chquery'), key: EQueryType.CLICKHOUSE },
{ label: t('tab_promql'), key: EQueryType.PROM.toString() }, { label: t('tab_promql'), key: EQueryType.PROM },
]; ];
const renderTabs = (typ: AlertTypes): JSX.Element | null => { const renderTabs = (typ: AlertTypes): JSX.Element | null => {
@ -309,16 +106,14 @@ function QuerySection({
<Tabs <Tabs
type="card" type="card"
style={{ width: '100%' }} style={{ width: '100%' }}
defaultActiveKey={EQueryType.CLICKHOUSE.toString()} defaultActiveKey={EQueryType.QUERY_BUILDER}
activeKey={queryCategory.toString()} activeKey={queryCategory}
onChange={handleQueryCategoryChange} onChange={handleQueryCategoryChange}
tabBarExtraContent={ tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}> <span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
{queryCategory === EQueryType.CLICKHOUSE && ( <Button type="primary" onClick={handleRunQuery}>
<Button type="primary" onClick={handleRunQuery}> Run Query
Run Query </Button>
</Button>
)}
</span> </span>
} }
items={tabs} items={tabs}
@ -330,16 +125,14 @@ function QuerySection({
<Tabs <Tabs
type="card" type="card"
style={{ width: '100%' }} style={{ width: '100%' }}
defaultActiveKey={EQueryType.QUERY_BUILDER.toString()} defaultActiveKey={EQueryType.QUERY_BUILDER}
activeKey={queryCategory.toString()} activeKey={queryCategory}
onChange={handleQueryCategoryChange} onChange={handleQueryCategoryChange}
tabBarExtraContent={ tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}> <span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
{queryCategory === EQueryType.CLICKHOUSE && ( <Button type="primary" onClick={handleRunQuery}>
<Button type="primary" onClick={handleRunQuery}> Run Query
Run Query </Button>
</Button>
)}
</span> </span>
} }
items={items} items={items}
@ -373,10 +166,6 @@ function QuerySection({
interface QuerySectionProps { interface QuerySectionProps {
queryCategory: EQueryType; queryCategory: EQueryType;
setQueryCategory: (n: EQueryType) => void; setQueryCategory: (n: EQueryType) => void;
metricQueries: IMetricQueries;
setMetricQueries: (b: IMetricQueries) => void;
formulaQueries: IFormulaQueries;
setFormulaQueries: (b: IFormulaQueries) => void;
promQueries: IPromQueries; promQueries: IPromQueries;
setPromQueries: (p: IPromQueries) => void; setPromQueries: (p: IPromQueries) => void;
chQueries: IChQueries; chQueries: IChQueries;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,8 +69,6 @@ function MetricsBuilder({
}); });
}, [metricName]); }, [metricName]);
// TODO: rewrite to Form component from antd
return ( return (
<QueryHeader <QueryHeader
name={queryData.name} 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 { Query } from 'types/api/dashboard/getAll';
import { import { WIDGET_QUERY_BUILDER_QUERY_KEY_NAME } from '../../constants';
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME,
WIDGET_QUERY_BUILDER_QUERY_KEY_NAME,
} from '../../constants';
const QUERY_AND_FORMULA_LIMIT = 10; const QUERY_AND_FORMULA_LIMIT = 10;
export const canCreateQueryAndFormula = (query: Query): boolean => { export const canCreateQueryAndFormula = (query: Query): boolean => {
const queries = query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder; const queries = query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryData;
const formulas = const formulas = query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryFormulas;
query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
];
return queries.length + formulas.length < QUERY_AND_FORMULA_LIMIT; 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 { 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 = export const WIDGET_CLICKHOUSE_QUERY_KEY_NAME = EQueryType.CLICKHOUSE;
EQueryTypeToQueryKeyMapping[EQueryType[EQueryType.PROM]];
export const WIDGET_CLICKHOUSE_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.CLICKHOUSE = EQueryTypeToQueryKeyMapping[ export const WIDGET_QUERY_BUILDER_QUERY_KEY_NAME = EQueryType.QUERY_BUILDER;
EQueryType[EQueryType.CLICKHOUSE]
] as string;
export const WIDGET_QUERY_BUILDER_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.QUERY_BUILDER = EQueryTypeToQueryKeyMapping[
EQueryType[EQueryType.QUERY_BUILDER]
] as string;
type TFormulas = 'formulas'; type TFormulas = 'formulas';
export const WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME: 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 PromQLQueryContainer from './QueryBuilder/promQL';
import TabHeader from './TabHeader'; import TabHeader from './TabHeader';
import { IHandleUpdatedQuery } from './types'; import { IHandleUpdatedQuery } from './types';
import { getQueryKey } from './utils/getQueryKey';
import { showUnstagedStashConfirmBox } from './utils/userSettings'; import { showUnstagedStashConfirmBox } from './utils/userSettings';
function QuerySection({ function QuerySection({
@ -76,15 +75,10 @@ function QuerySection({
queryA: Query, queryA: Query,
queryB: Query, queryB: Query,
queryCategory: EQueryType, queryCategory: EQueryType,
): boolean => { ): boolean => !isEqual(queryA[queryCategory], queryB[queryCategory]);
const keyOfConcern = getQueryKey(queryCategory);
return !isEqual(queryA[keyOfConcern], queryB[keyOfConcern]);
};
useEffect(() => { useEffect(() => {
handleUnstagedChanges( handleUnstagedChanges(queryDiff(query, localQueryChanges, queryCategory));
queryDiff(query, localQueryChanges, parseInt(`${queryCategory}`, 10)),
);
}, [handleUnstagedChanges, localQueryChanges, query, queryCategory]); }, [handleUnstagedChanges, localQueryChanges, query, queryCategory]);
const regenRctKeys = (): void => { const regenRctKeys = (): void => {
@ -111,11 +105,7 @@ function QuerySection({
const handleQueryCategoryChange = (qCategory: string): void => { const handleQueryCategoryChange = (qCategory: string): void => {
// If true, then it means that the user has made some changes and haven't staged them // If true, then it means that the user has made some changes and haven't staged them
const unstagedChanges = queryDiff( const unstagedChanges = queryDiff(query, localQueryChanges, queryCategory);
query,
localQueryChanges,
parseInt(`${queryCategory}`, 10),
);
if (unstagedChanges && showUnstagedStashConfirmBox()) { if (unstagedChanges && showUnstagedStashConfirmBox()) {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
@ -125,10 +115,10 @@ function QuerySection({
return; return;
} }
setQueryCategory(parseInt(`${qCategory}`, 10)); setQueryCategory(qCategory as EQueryType);
const newLocalQuery = { const newLocalQuery = {
...cloneDeep(query), ...cloneDeep(query),
queryType: parseInt(`${qCategory}`, 10), queryType: qCategory as EQueryType,
}; };
setLocalQueryChanges(newLocalQuery); setLocalQueryChanges(newLocalQuery);
regenRctKeys(); regenRctKeys();
@ -147,7 +137,7 @@ function QuerySection({
const items = [ const items = [
{ {
key: EQueryType.QUERY_BUILDER.toString(), key: EQueryType.QUERY_BUILDER,
label: 'Query Builder', label: 'Query Builder',
tab: ( tab: (
<TabHeader <TabHeader
@ -162,7 +152,7 @@ function QuerySection({
children: <QueryBuilder panelType={selectedGraph} />, children: <QueryBuilder panelType={selectedGraph} />,
}, },
{ {
key: EQueryType.CLICKHOUSE.toString(), key: EQueryType.CLICKHOUSE,
label: 'ClickHouse Query', label: 'ClickHouse Query',
tab: ( tab: (
<TabHeader <TabHeader
@ -186,7 +176,7 @@ function QuerySection({
), ),
}, },
{ {
key: EQueryType.PROM.toString(), key: EQueryType.PROM,
label: 'PromQL', label: 'PromQL',
tab: ( tab: (
<TabHeader <TabHeader
@ -213,8 +203,8 @@ function QuerySection({
<Tabs <Tabs
type="card" type="card"
style={{ width: '100%' }} style={{ width: '100%' }}
defaultActiveKey={queryCategory.toString()} defaultActiveKey={queryCategory}
activeKey={queryCategory.toString()} activeKey={queryCategory}
onChange={handleQueryCategoryChange} onChange={handleQueryCategoryChange}
tabBarExtraContent={ tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}> <span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ export const convertNewDataToOld = (
QueryData['values'] QueryData['values']
>((acc, currentInfo) => { >((acc, currentInfo) => {
const renderValues: [number, string] = [ const renderValues: [number, string] = [
currentInfo.timestamp, currentInfo.timestamp / 1000,
currentInfo.value, 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'; import { QueryBuilderData } from 'types/common/queryBuilder';
import {
type MapQueryDataToApiResult = { MapFormula,
data: Record<string, IBuilderQuery | IBuilderFormula>; MapQuery,
newLegendMap: Record<string, string>; MapQueryDataToApiResult,
}; } from 'types/common/queryBuilderMappers.types';
type MapQuery = Record<string, IBuilderQuery>;
type MapFormula = Record<string, IBuilderFormula>;
export const mapQueryDataToApi = ( export const mapQueryDataToApi = (
data: QueryBuilderData, data: QueryBuilderData,
@ -41,6 +35,8 @@ export const mapQueryDataToApi = (
}, },
}; };
newLegendMap[formula.queryName] = formula.legend;
return newResult; return newResult;
}, },
{}, {},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ export interface AlertDef {
} }
export interface RuleCondition { export interface RuleCondition {
compositeMetricQuery: ICompositeMetricQuery; compositeQuery: ICompositeMetricQuery;
op?: string | undefined; op?: string | undefined;
target?: number | undefined; target?: number | undefined;
matchType?: string | 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 { export interface Query {
queryType: EQueryType; queryType: EQueryType;
promQL: IPromQLQuery[]; promql: IPromQLQuery[];
builder: QueryBuilderData; builder: QueryBuilderData;
clickHouse: IClickHouseQuery[]; clickhouse_sql: IClickHouseQuery[];
} }
export interface IMetricsBuilderFormula { 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 DataType = 'int64' | 'float64' | 'string' | 'bool';
export type AutocompleteType = 'tag' | 'resource';
export interface BaseAutocompleteData { export interface BaseAutocompleteData {
dataType: DataType | null; dataType: DataType | null;
isColumn: boolean | null; isColumn: boolean | null;
key: string; key: string;
type: 'tag' | 'resource' | null; type: AutocompleteType | null;
} }
export interface IQueryAutocompleteResponse { export interface IQueryAutocompleteResponse {

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { import {
IBuilderFormula, IBuilderFormula,
IBuilderQuery, IBuilderQuery,
@ -45,7 +46,6 @@ export enum NumberOperators {
HIST_QUANTILE_99 = 'hist_quantile_99', HIST_QUANTILE_99 = 'hist_quantile_99',
} }
// TODO: add boolean operators from backend
export enum BoolOperators { export enum BoolOperators {
NOOP = 'noop', NOOP = 'noop',
COUNT = 'count', COUNT = 'count',
@ -147,13 +147,20 @@ export type QueryBuilderData = {
queryFormulas: IBuilderFormula[]; 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 = { export type QueryBuilderContextType = {
queryBuilderData: QueryBuilderData; queryBuilderData: QueryBuilderData;
initialDataSource: DataSource | null; initialDataSource: DataSource | null;
panelType: GRAPH_TYPES;
resetQueryBuilderData: () => void; resetQueryBuilderData: () => void;
resetQueryBuilderInfo: () => void;
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void; handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void; handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
handleSetPanelType: (newPanelType: GRAPH_TYPES) => void;
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void; initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
setupInitialDataSource: (newInitialDataSource: DataSource | null) => void; setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
removeEntityByIndex: (type: keyof QueryBuilderData, index: number) => 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; value: Value;
label: Label; label: Label;
}; };
export type ExtendedSelectOption = {
disabled?: boolean;
key: string;
label: string;
title?: string;
value: string;
};