mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 15:55:54 +08:00
feat: [UI] clickhouse queries in alert builder (#1706)
* feat: added ui changes to support clickhouse queries in alert builder * chore: minor fix to alert rules ui * feat: alert form changes: ch query support, alert type selection * chore: resolved review comments * chore: added list for alert type selection instead * chore: removed hard coded color and added antd/colors * fix: resolved some issues found during testing alerts * fix: moved alert defaults and added default queries for logs and traces * feat: added default queries for logs and traces to reflect ts vars * chore: fixed px to rem Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Pranay Prateek <pranay@signoz.io>
This commit is contained in:
parent
4727dbc9f0
commit
220f848b04
@ -28,6 +28,7 @@
|
||||
"condition_required": "at least one metric condition is required",
|
||||
"alertname_required": "alert name is required",
|
||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
||||
"chquery_required": "query is required when query format is set to ClickHouse",
|
||||
"button_savechanges": "Save Rule",
|
||||
"button_createrule": "Create Rule",
|
||||
"button_returntorules": "Return to rules",
|
||||
@ -55,6 +56,7 @@
|
||||
"button_formula": "Formula",
|
||||
"tab_qb": "Query Builder",
|
||||
"tab_promql": "PromQL",
|
||||
"tab_chquery": "ClickHouse Query",
|
||||
"title_confirm": "Confirm",
|
||||
"button_ok": "Yes",
|
||||
"button_cancel": "No",
|
||||
@ -88,5 +90,21 @@
|
||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_pql_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts"
|
||||
"user_guide_ch_step1": "Step 1 - Define the metric",
|
||||
"user_guide_ch_step1a": "Write a Clickhouse query for alert evaluation. Follow <0>this tutorial</0> to learn about query format and supported vars.",
|
||||
"user_guide_ch_step1b": "Format the legends based on labels you want to highlight in the preview chart",
|
||||
"user_guide_ch_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_ch_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_ch_step2b": "Enter the Alert threshold",
|
||||
"user_guide_ch_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert:",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in metric data",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
"traces_based_alert_desc": "Send a notification when a condition occurs in traces data."
|
||||
}
|
@ -28,6 +28,7 @@
|
||||
"condition_required": "at least one metric condition is required",
|
||||
"alertname_required": "alert name is required",
|
||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
||||
"chquery_required": "query is required when query format is set to ClickHouse",
|
||||
"button_savechanges": "Save Rule",
|
||||
"button_createrule": "Create Rule",
|
||||
"button_returntorules": "Return to rules",
|
||||
@ -55,6 +56,7 @@
|
||||
"button_formula": "Formula",
|
||||
"tab_qb": "Query Builder",
|
||||
"tab_promql": "PromQL",
|
||||
"tab_chquery": "ClickHouse Query",
|
||||
"title_confirm": "Confirm",
|
||||
"button_ok": "Yes",
|
||||
"button_cancel": "No",
|
||||
@ -88,5 +90,21 @@
|
||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_pql_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts"
|
||||
"user_guide_ch_step1": "Step 1 - Define the metric",
|
||||
"user_guide_ch_step1a": "Write a Clickhouse query for alert evaluation. Follow <0>this tutorial</0> to learn about query format and supported vars.",
|
||||
"user_guide_ch_step1b": "Format the legends based on labels you want to highlight in the preview chart",
|
||||
"user_guide_ch_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_ch_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_ch_step2b": "Enter the Alert threshold",
|
||||
"user_guide_ch_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert:",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in metric data",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
"traces_based_alert_desc": "Send a notification when a condition occurs in traces data."
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
import { AlertTypeCard, AlertTypeCards, SelectTypeContainer } from './styles';
|
||||
|
||||
interface OptionType {
|
||||
title: string;
|
||||
selection: AlertTypes;
|
||||
description: string;
|
||||
}
|
||||
|
||||
function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
|
||||
const { t } = useTranslation(['alerts']);
|
||||
|
||||
const renderOptions = (): JSX.Element => {
|
||||
const optionList: OptionType[] = [
|
||||
{
|
||||
title: t('metric_based_alert'),
|
||||
selection: AlertTypes.METRICS_BASED_ALERT,
|
||||
description: t('metric_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('log_based_alert'),
|
||||
selection: AlertTypes.LOGS_BASED_ALERT,
|
||||
description: t('log_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('traces_based_alert'),
|
||||
selection: AlertTypes.TRACES_BASED_ALERT,
|
||||
description: t('traces_based_alert_desc'),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{optionList.map((o: OptionType) => (
|
||||
<AlertTypeCard
|
||||
key={o.selection}
|
||||
title={o.title}
|
||||
onClick={(): void => {
|
||||
onSelect(o.selection);
|
||||
}}
|
||||
>
|
||||
{o.description}
|
||||
</AlertTypeCard>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<SelectTypeContainer>
|
||||
<h3> {t('choose_alert_type')} </h3>
|
||||
<AlertTypeCards>{renderOptions()}</AlertTypeCards>
|
||||
</SelectTypeContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface SelectAlertTypeProps {
|
||||
onSelect: (typ: AlertTypes) => void;
|
||||
}
|
||||
|
||||
export default SelectAlertType;
|
@ -0,0 +1,22 @@
|
||||
import { Card, Row } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SelectTypeContainer = styled.div`
|
||||
&&& {
|
||||
padding: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AlertTypeCards = styled(Row)`
|
||||
&&& {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AlertTypeCard = styled(Card)`
|
||||
&&& {
|
||||
margin: 5px;
|
||||
width: 21rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
139
frontend/src/container/CreateAlertRule/defaults.ts
Normal file
139
frontend/src/container/CreateAlertRule/defaults.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import {
|
||||
AlertDef,
|
||||
defaultCompareOp,
|
||||
defaultEvalWindow,
|
||||
defaultMatchType,
|
||||
} from 'types/api/alerts/def';
|
||||
|
||||
export const alertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||
condition: {
|
||||
compositeMetricQuery: {
|
||||
builderQueries: {
|
||||
A: {
|
||||
queryName: 'A',
|
||||
name: 'A',
|
||||
formulaOnly: false,
|
||||
metricName: '',
|
||||
tagFilters: {
|
||||
op: 'AND',
|
||||
items: [],
|
||||
},
|
||||
groupBy: [],
|
||||
aggregateOperator: 1,
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
toggleDisable: false,
|
||||
toggleDelete: false,
|
||||
},
|
||||
},
|
||||
promQueries: {},
|
||||
chQueries: {},
|
||||
queryType: 1,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: defaultMatchType,
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
},
|
||||
annotations: {
|
||||
description: 'A new alert',
|
||||
},
|
||||
evalWindow: defaultEvalWindow,
|
||||
};
|
||||
|
||||
export const logAlertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.LOGS_BASED_ALERT,
|
||||
condition: {
|
||||
compositeMetricQuery: {
|
||||
builderQueries: {
|
||||
A: {
|
||||
queryName: 'A',
|
||||
name: 'A',
|
||||
formulaOnly: false,
|
||||
metricName: '',
|
||||
tagFilters: {
|
||||
op: 'AND',
|
||||
items: [],
|
||||
},
|
||||
groupBy: [],
|
||||
aggregateOperator: 1,
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
toggleDisable: false,
|
||||
toggleDelete: false,
|
||||
},
|
||||
},
|
||||
promQueries: {},
|
||||
chQueries: {
|
||||
A: {
|
||||
name: 'A',
|
||||
query: `select \ntoStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 MINUTE) AS interval, \ntoFloat64(count()) as value \nFROM signoz_logs.logs \nWHERE timestamp BETWEEN {{.start_timestamp_nano}} AND {{.end_timestamp_nano}} \nGROUP BY interval;\n\n-- available variables:\n-- \t{{.start_timestamp_nano}}\n-- \t{{.end_timestamp_nano}}\n\n-- required columns (or alias):\n-- \tvalue\n-- \tinterval`,
|
||||
rawQuery: `select \ntoStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 MINUTE) AS interval, \ntoFloat64(count()) as value \nFROM signoz_logs.logs \nWHERE timestamp BETWEEN {{.start_timestamp_nano}} AND {{.end_timestamp_nano}} \nGROUP BY interval;\n\n-- available variables:\n-- \t{{.start_timestamp_nano}}\n-- \t{{.end_timestamp_nano}}\n\n-- required columns (or alias):\n-- \tvalue\n-- \tinterval`,
|
||||
legend: '',
|
||||
disabled: false,
|
||||
},
|
||||
},
|
||||
queryType: 2,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: '4',
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
details: `${window.location.protocol}//${window.location.host}/logs`,
|
||||
},
|
||||
annotations: {
|
||||
description: 'A new log-based alert',
|
||||
},
|
||||
evalWindow: defaultEvalWindow,
|
||||
};
|
||||
|
||||
export const traceAlertDefaults: AlertDef = {
|
||||
alertType: AlertTypes.TRACES_BASED_ALERT,
|
||||
condition: {
|
||||
compositeMetricQuery: {
|
||||
builderQueries: {
|
||||
A: {
|
||||
queryName: 'A',
|
||||
name: 'A',
|
||||
formulaOnly: false,
|
||||
metricName: '',
|
||||
tagFilters: {
|
||||
op: 'AND',
|
||||
items: [],
|
||||
},
|
||||
groupBy: [],
|
||||
aggregateOperator: 1,
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
toggleDisable: false,
|
||||
toggleDelete: false,
|
||||
},
|
||||
},
|
||||
promQueries: {},
|
||||
chQueries: {
|
||||
A: {
|
||||
name: 'A',
|
||||
rawQuery: `SELECT \n\tcount() as value,\n\ttoStartOfInterval(timestamp, toIntervalMinute(1)) AS interval,\n\tserviceName\nFROM signoz_traces.signoz_error_index_v2\nWHERE exceptionType !='OSError'\nAND timestamp BETWEEN {{.start_datetime}} AND {{.end_datetime}}\nGROUP BY serviceName, interval;\n\n-- available variables:\n-- \t{{.start_datetime}}\n-- \t{{.end_datetime}}\n\n-- required column alias:\n-- \tvalue\n-- \tinterval`,
|
||||
query: `SELECT \n\tcount() as value,\n\ttoStartOfInterval(timestamp, toIntervalMinute(1)) AS interval,\n\tserviceName\nFROM signoz_traces.signoz_error_index_v2\nWHERE exceptionType !='OSError'\nAND timestamp BETWEEN {{.start_datetime}} AND {{.end_datetime}}\nGROUP BY serviceName, interval;\n\n-- available variables:\n-- \t{{.start_datetime}}\n-- \t{{.end_datetime}}\n\n-- required column alias:\n-- \tvalue\n-- \tinterval`,
|
||||
legend: '',
|
||||
disabled: false,
|
||||
},
|
||||
},
|
||||
queryType: 2,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: '4',
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
details: `${window.location.protocol}//${window.location.host}/traces`,
|
||||
},
|
||||
annotations: {
|
||||
description: 'A new trace-based alert',
|
||||
},
|
||||
evalWindow: defaultEvalWindow,
|
||||
};
|
@ -1,22 +1,53 @@
|
||||
import { Form } from 'antd';
|
||||
import { Form, Row } from 'antd';
|
||||
import FormAlertRules from 'container/FormAlertRules';
|
||||
import React from 'react';
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
import React, { useState } from 'react';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
function CreateRules({ initialValue }: CreateRulesProps): JSX.Element {
|
||||
import {
|
||||
alertDefaults,
|
||||
logAlertDefaults,
|
||||
traceAlertDefaults,
|
||||
} from './defaults';
|
||||
import SelectAlertType from './SelectAlertType';
|
||||
|
||||
function CreateRules(): JSX.Element {
|
||||
const [initValues, setInitValues] = useState(alertDefaults);
|
||||
const [step, setStep] = useState(0);
|
||||
const [alertType, setAlertType] = useState<AlertTypes>(
|
||||
AlertTypes.METRICS_BASED_ALERT,
|
||||
);
|
||||
const [formInstance] = Form.useForm();
|
||||
|
||||
const onSelectType = (typ: AlertTypes): void => {
|
||||
setAlertType(typ);
|
||||
switch (typ) {
|
||||
case AlertTypes.LOGS_BASED_ALERT:
|
||||
setInitValues(logAlertDefaults);
|
||||
break;
|
||||
case AlertTypes.TRACES_BASED_ALERT:
|
||||
setInitValues(traceAlertDefaults);
|
||||
break;
|
||||
default:
|
||||
setInitValues(alertDefaults);
|
||||
}
|
||||
setStep(1);
|
||||
};
|
||||
|
||||
if (step === 0) {
|
||||
return (
|
||||
<Row wrap={false}>
|
||||
<SelectAlertType onSelect={onSelectType} />
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FormAlertRules
|
||||
alertType={alertType}
|
||||
formInstance={formInstance}
|
||||
initialValue={initialValue}
|
||||
initialValue={initValues}
|
||||
ruleId={0}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface CreateRulesProps {
|
||||
initialValue: AlertDef;
|
||||
}
|
||||
|
||||
export default CreateRules;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Form } from 'antd';
|
||||
import FormAlertRules from 'container/FormAlertRules';
|
||||
import React from 'react';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
|
||||
function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
||||
@ -8,6 +9,11 @@ function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<FormAlertRules
|
||||
alertType={
|
||||
initialValue.alertType
|
||||
? (initialValue.alertType as AlertTypes)
|
||||
: AlertTypes.METRICS_BASED_ALERT
|
||||
}
|
||||
formInstance={formInstance}
|
||||
initialValue={initialValue}
|
||||
ruleId={ruleId}
|
||||
|
53
frontend/src/container/FormAlertRules/ChQuerySection.tsx
Normal file
53
frontend/src/container/FormAlertRules/ChQuerySection.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import ClickHouseQueryBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query';
|
||||
import { IClickHouseQueryHandleChange } from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/types';
|
||||
import React from 'react';
|
||||
import { IChQueries } from 'types/api/alerts/compositeQuery';
|
||||
|
||||
function ChQuerySection({
|
||||
chQueries,
|
||||
setChQueries,
|
||||
}: ChQuerySectionProps): JSX.Element {
|
||||
const handleChQueryChange = ({
|
||||
rawQuery,
|
||||
legend,
|
||||
toggleDelete,
|
||||
}: IClickHouseQueryHandleChange): void => {
|
||||
let chQuery = chQueries.A;
|
||||
|
||||
if (rawQuery) {
|
||||
chQuery.rawQuery = rawQuery;
|
||||
chQuery.query = rawQuery;
|
||||
}
|
||||
|
||||
if (legend) chQuery.legend = legend;
|
||||
if (toggleDelete) {
|
||||
chQuery = {
|
||||
rawQuery: '',
|
||||
legend: '',
|
||||
name: 'A',
|
||||
disabled: false,
|
||||
query: '',
|
||||
};
|
||||
}
|
||||
setChQueries({
|
||||
A: {
|
||||
...chQuery,
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<ClickHouseQueryBuilder
|
||||
key="A"
|
||||
queryIndex="A"
|
||||
queryData={{ ...chQueries?.A, name: 'A', rawQuery: chQueries?.A.query }}
|
||||
handleQueryChange={handleChQueryChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface ChQuerySectionProps {
|
||||
chQueries: IChQueries;
|
||||
setChQueries: (q: IChQueries) => void;
|
||||
}
|
||||
|
||||
export default ChQuerySection;
|
@ -1,11 +1,12 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { StaticLineProps } from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import GridGraphComponent from 'container/GridGraphComponent';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
||||
@ -22,6 +23,10 @@ export interface ChartPreviewProps {
|
||||
selectedInterval?: Time;
|
||||
headline?: JSX.Element;
|
||||
threshold?: number | undefined;
|
||||
userQueryKey?: string;
|
||||
}
|
||||
interface QueryResponseError {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
function ChartPreview({
|
||||
@ -32,6 +37,7 @@ function ChartPreview({
|
||||
selectedInterval = '5min',
|
||||
headline,
|
||||
threshold,
|
||||
userQueryKey,
|
||||
}: ChartPreviewProps): JSX.Element | null {
|
||||
const { t } = useTranslation('alerts');
|
||||
const staticLine: StaticLineProps | undefined =
|
||||
@ -46,9 +52,34 @@ function ChartPreview({
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const queryKey = JSON.stringify(query);
|
||||
const canQuery = useMemo((): boolean => {
|
||||
if (!query || query == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (query?.queryType) {
|
||||
case EQueryType.PROM:
|
||||
return query.promQL?.length > 0 && query.promQL[0].query !== '';
|
||||
case EQueryType.CLICKHOUSE:
|
||||
return (
|
||||
query.clickHouse?.length > 0 && query.clickHouse[0].rawQuery?.length > 0
|
||||
);
|
||||
case EQueryType.QUERY_BUILDER:
|
||||
return (
|
||||
query.metricsBuilder?.queryBuilder?.length > 0 &&
|
||||
query.metricsBuilder?.queryBuilder[0].metricName !== ''
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
const queryResponse = useQuery({
|
||||
queryKey: ['chartPreview', queryKey, selectedInterval],
|
||||
queryKey: [
|
||||
'chartPreview',
|
||||
userQueryKey || JSON.stringify(query),
|
||||
selectedInterval,
|
||||
],
|
||||
queryFn: () =>
|
||||
GetMetricQueryRange({
|
||||
query: query || {
|
||||
@ -64,14 +95,8 @@ function ChartPreview({
|
||||
graphType,
|
||||
selectedTime,
|
||||
}),
|
||||
enabled:
|
||||
query != null &&
|
||||
((query.queryType === EQueryType.PROM &&
|
||||
query.promQL?.length > 0 &&
|
||||
query.promQL[0].query !== '') ||
|
||||
(query.queryType === EQueryType.QUERY_BUILDER &&
|
||||
query.metricsBuilder?.queryBuilder?.length > 0 &&
|
||||
query.metricsBuilder?.queryBuilder[0].metricName !== '')),
|
||||
retry: false,
|
||||
enabled: canQuery,
|
||||
});
|
||||
|
||||
const chartDataSet = queryResponse.isError
|
||||
@ -89,15 +114,14 @@ function ChartPreview({
|
||||
return (
|
||||
<ChartContainer>
|
||||
{headline}
|
||||
{(queryResponse?.data?.error || queryResponse?.isError) && (
|
||||
{(queryResponse?.isError || queryResponse?.error) && (
|
||||
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
||||
<InfoCircleOutlined />{' '}
|
||||
{queryResponse?.data?.error ||
|
||||
queryResponse?.error ||
|
||||
{(queryResponse?.error as QueryResponseError).message ||
|
||||
t('preview_chart_unexpected_error')}
|
||||
</FailedMessageContainer>
|
||||
)}
|
||||
|
||||
{queryResponse.isLoading && <Spinner size="large" tip="Loading..." />}
|
||||
{chartDataSet && !queryResponse.isError && (
|
||||
<GridGraphComponent
|
||||
title={name}
|
||||
@ -118,6 +142,7 @@ ChartPreview.defaultProps = {
|
||||
selectedInterval: '5min',
|
||||
headline: undefined,
|
||||
threshold: undefined,
|
||||
userQueryKey: '',
|
||||
};
|
||||
|
||||
export default ChartPreview;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { red } from '@ant-design/colors';
|
||||
import { Card, Tooltip } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -10,7 +11,8 @@ export const NotFoundContainer = styled.div`
|
||||
|
||||
export const FailedMessageContainer = styled(Tooltip)`
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
color: ${red};
|
||||
top: 4rem;
|
||||
left: 10px;
|
||||
`;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { notification, Tabs } from 'antd';
|
||||
import { Button, notification, Tabs } from 'antd';
|
||||
import MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula';
|
||||
import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query';
|
||||
import {
|
||||
@ -8,13 +8,16 @@ import {
|
||||
} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import {
|
||||
IChQueries,
|
||||
IFormulaQueries,
|
||||
IMetricQueries,
|
||||
IPromQueries,
|
||||
} from 'types/api/alerts/compositeQuery';
|
||||
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import ChQuerySection from './ChQuerySection';
|
||||
import PromqlSection from './PromqlSection';
|
||||
import { FormContainer, QueryButton, StepHeading } from './styles';
|
||||
import { toIMetricsBuilderQuery } from './utils';
|
||||
@ -29,6 +32,10 @@ function QuerySection({
|
||||
setFormulaQueries,
|
||||
promQueries,
|
||||
setPromQueries,
|
||||
chQueries,
|
||||
setChQueries,
|
||||
alertType,
|
||||
runQuery,
|
||||
}: QuerySectionProps): JSX.Element {
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('alerts');
|
||||
@ -49,6 +56,20 @@ function QuerySection({
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
parseInt(s, 10) === EQueryType.CLICKHOUSE &&
|
||||
(!chQueries || Object.keys(chQueries).length === 0)
|
||||
) {
|
||||
setChQueries({
|
||||
A: {
|
||||
rawQuery: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
legend: '',
|
||||
disabled: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
setQueryCategory(parseInt(s, 10));
|
||||
};
|
||||
|
||||
@ -196,6 +217,10 @@ function QuerySection({
|
||||
);
|
||||
};
|
||||
|
||||
const renderChQueryUI = (): JSX.Element => {
|
||||
return <ChQuerySection chQueries={chQueries} setChQueries={setChQueries} />;
|
||||
};
|
||||
|
||||
const renderFormulaButton = (): JSX.Element => {
|
||||
return (
|
||||
<QueryButton onClick={addFormula} icon={<PlusOutlined />}>
|
||||
@ -258,23 +283,84 @@ function QuerySection({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||
<FormContainer>
|
||||
<div style={{ display: 'flex' }}>
|
||||
|
||||
const handleRunQuery = (): void => {
|
||||
runQuery();
|
||||
};
|
||||
|
||||
const renderTabs = (typ: AlertTypes): JSX.Element | null => {
|
||||
switch (typ) {
|
||||
case AlertTypes.TRACES_BASED_ALERT:
|
||||
case AlertTypes.LOGS_BASED_ALERT:
|
||||
return (
|
||||
<Tabs
|
||||
type="card"
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveKey={EQueryType.CLICKHOUSE.toString()}
|
||||
activeKey={queryCategory.toString()}
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
{queryCategory === EQueryType.CLICKHOUSE && (
|
||||
<Button type="primary" onClick={handleRunQuery}>
|
||||
Run Query
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<TabPane
|
||||
tab={t('tab_qb')}
|
||||
key={EQueryType.QUERY_BUILDER.toString()}
|
||||
disabled
|
||||
/>
|
||||
<TabPane tab={t('tab_chquery')} key={EQueryType.CLICKHOUSE.toString()} />
|
||||
</Tabs>
|
||||
);
|
||||
case AlertTypes.METRICS_BASED_ALERT:
|
||||
default:
|
||||
return (
|
||||
<Tabs
|
||||
type="card"
|
||||
style={{ width: '100%' }}
|
||||
defaultActiveKey={EQueryType.QUERY_BUILDER.toString()}
|
||||
activeKey={queryCategory.toString()}
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
{queryCategory === EQueryType.CLICKHOUSE && (
|
||||
<Button type="primary" onClick={handleRunQuery}>
|
||||
Run Query
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<TabPane tab={t('tab_qb')} key={EQueryType.QUERY_BUILDER.toString()} />
|
||||
<TabPane tab={t('tab_chquery')} key={EQueryType.CLICKHOUSE.toString()} />
|
||||
<TabPane tab={t('tab_promql')} key={EQueryType.PROM.toString()} />
|
||||
</Tabs>
|
||||
</div>
|
||||
{queryCategory === EQueryType.PROM ? renderPromqlUI() : renderMetricUI()}
|
||||
);
|
||||
}
|
||||
};
|
||||
const renderQuerySection = (c: EQueryType): JSX.Element | null => {
|
||||
switch (c) {
|
||||
case EQueryType.PROM:
|
||||
return renderPromqlUI();
|
||||
case EQueryType.CLICKHOUSE:
|
||||
return renderChQueryUI();
|
||||
case EQueryType.QUERY_BUILDER:
|
||||
return renderMetricUI();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||
<FormContainer>
|
||||
<div style={{ display: 'flex' }}>{renderTabs(alertType)}</div>
|
||||
{renderQuerySection(queryCategory)}
|
||||
</FormContainer>
|
||||
</>
|
||||
);
|
||||
@ -289,6 +375,10 @@ interface QuerySectionProps {
|
||||
setFormulaQueries: (b: IFormulaQueries) => void;
|
||||
promQueries: IPromQueries;
|
||||
setPromQueries: (p: IPromQueries) => void;
|
||||
chQueries: IChQueries;
|
||||
setChQueries: (q: IChQueries) => void;
|
||||
alertType: AlertTypes;
|
||||
runQuery: () => void;
|
||||
}
|
||||
|
||||
export default QuerySection;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Col, Row, Typography } from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import {
|
||||
@ -106,6 +106,63 @@ function UserGuide({ queryType }: UserGuideProps): JSX.Element {
|
||||
);
|
||||
};
|
||||
|
||||
const renderStep1CH = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<StyledTopic>{t('user_guide_ch_step1')}</StyledTopic>
|
||||
<StyledList>
|
||||
<StyledListItem>
|
||||
<Trans
|
||||
i18nKey="user_guide_ch_step1a"
|
||||
t={t}
|
||||
components={[
|
||||
// eslint-disable-next-line jsx-a11y/control-has-associated-label, jsx-a11y/anchor-has-content
|
||||
<a
|
||||
key={1}
|
||||
target="_blank"
|
||||
href=" https://signoz.io/docs/tutorial/writing-clickhouse-queries-in-dashboard/?utm_source=frontend&utm_medium=product&utm_id=alerts</>"
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</StyledListItem>
|
||||
<StyledListItem>{t('user_guide_ch_step1b')}</StyledListItem>
|
||||
</StyledList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const renderStep2CH = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<StyledTopic>{t('user_guide_ch_step2')}</StyledTopic>
|
||||
<StyledList>
|
||||
<StyledListItem>{t('user_guide_ch_step2a')}</StyledListItem>
|
||||
<StyledListItem>{t('user_guide_ch_step2b')}</StyledListItem>
|
||||
</StyledList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderStep3CH = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<StyledTopic>{t('user_guide_ch_step3')}</StyledTopic>
|
||||
<StyledList>
|
||||
<StyledListItem>{t('user_guide_ch_step3a')}</StyledListItem>
|
||||
<StyledListItem>{t('user_guide_ch_step3b')}</StyledListItem>
|
||||
</StyledList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderGuideForCH = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
{renderStep1CH()}
|
||||
{renderStep2CH()}
|
||||
{renderStep3CH()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<StyledMainContainer>
|
||||
<Row>
|
||||
@ -121,6 +178,7 @@ function UserGuide({ queryType }: UserGuideProps): JSX.Element {
|
||||
</Row>
|
||||
{queryType === EQueryType.QUERY_BUILDER && renderGuideForQB()}
|
||||
{queryType === EQueryType.PROM && renderGuideForPQL()}
|
||||
{queryType === EQueryType.CLICKHOUSE && renderGuideForCH()}
|
||||
</StyledMainContainer>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,9 @@ import history from 'lib/history';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import {
|
||||
IChQueries,
|
||||
IFormulaQueries,
|
||||
IMetricQueries,
|
||||
IPromQueries,
|
||||
@ -45,6 +47,7 @@ import {
|
||||
} from './utils';
|
||||
|
||||
function FormAlertRules({
|
||||
alertType,
|
||||
formInstance,
|
||||
initialValue,
|
||||
ruleId,
|
||||
@ -57,6 +60,10 @@ function FormAlertRules({
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// queryRunId helps to override of query caching for clickhouse query
|
||||
// tab. A random string will be assigned for each execution
|
||||
const [runQueryId, setRunQueryId] = useState<string>();
|
||||
|
||||
// alertDef holds the form values to be posted
|
||||
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
|
||||
|
||||
@ -82,9 +89,31 @@ function FormAlertRules({
|
||||
...initQuery?.promQueries,
|
||||
});
|
||||
|
||||
// staged query is used to display chart preview
|
||||
// local state to handle promql queries
|
||||
const [chQueries, setChQueries] = useState<IChQueries>({
|
||||
...initQuery?.chQueries,
|
||||
});
|
||||
|
||||
// staged query is used to display chart preview. the query gets
|
||||
// auto refreshed when any of the params in query section change.
|
||||
// though this is the source of chart data, the final query used
|
||||
// by chart will be either debouncedStagedQuery or manualStagedQuery
|
||||
// depending on the run option (auto-run or use of run query button)
|
||||
const [stagedQuery, setStagedQuery] = useState<StagedQuery>();
|
||||
const debouncedStagedQuery = useDebounce(stagedQuery, 1000);
|
||||
|
||||
// manualStagedQuery requires manual staging of query
|
||||
// when user clicks run query button. Useful for clickhouse tab where
|
||||
// run query button is provided.
|
||||
const [manualStagedQuery, setManualStagedQuery] = useState<StagedQuery>();
|
||||
|
||||
// delay to reduce load on backend api with auto-run query. only for clickhouse
|
||||
// queries we have manual run, hence both debounce and debounceStagedQuery are not required
|
||||
const debounceDelay = queryCategory !== EQueryType.CLICKHOUSE ? 1000 : 0;
|
||||
|
||||
// debounce query to delay backend api call and chart update.
|
||||
// used in query builder and promql tabs to enable auto-refresh
|
||||
// of chart on user edit
|
||||
const debouncedStagedQuery = useDebounce(stagedQuery, debounceDelay);
|
||||
|
||||
// this use effect initiates staged query and
|
||||
// other queries based on server data.
|
||||
@ -101,14 +130,26 @@ function FormAlertRules({
|
||||
const fq = toFormulaQueries(initQuery?.builderQueries);
|
||||
|
||||
// prepare staged query
|
||||
const sq = prepareStagedQuery(typ, mq, fq, initQuery?.promQueries);
|
||||
const sq = prepareStagedQuery(
|
||||
typ,
|
||||
mq,
|
||||
fq,
|
||||
initQuery?.promQueries,
|
||||
initQuery?.chQueries,
|
||||
);
|
||||
const pq = initQuery?.promQueries;
|
||||
const chq = initQuery?.chQueries;
|
||||
|
||||
setQueryCategory(typ);
|
||||
setMetricQueries(mq);
|
||||
setFormulaQueries(fq);
|
||||
setPromQueries(pq);
|
||||
setStagedQuery(sq);
|
||||
|
||||
// also set manually staged query
|
||||
setManualStagedQuery(sq);
|
||||
|
||||
setChQueries(chq);
|
||||
setAlertDef(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
@ -121,9 +162,15 @@ function FormAlertRules({
|
||||
metricQueries,
|
||||
formulaQueries,
|
||||
promQueries,
|
||||
chQueries,
|
||||
);
|
||||
setStagedQuery(sq);
|
||||
}, [queryCategory, metricQueries, formulaQueries, promQueries]);
|
||||
}, [queryCategory, chQueries, metricQueries, formulaQueries, promQueries]);
|
||||
|
||||
const onRunQuery = (): void => {
|
||||
setRunQueryId(Math.random().toString(36).substring(2, 15));
|
||||
setManualStagedQuery(stagedQuery);
|
||||
};
|
||||
|
||||
const onCancelHandler = useCallback(() => {
|
||||
history.replace(ROUTES.LIST_ALL_ALERT);
|
||||
@ -169,6 +216,31 @@ function FormAlertRules({
|
||||
return retval;
|
||||
}, [t, promQueries, queryCategory]);
|
||||
|
||||
const validateChQueryParams = useCallback((): boolean => {
|
||||
let retval = true;
|
||||
if (queryCategory !== EQueryType.CLICKHOUSE) return retval;
|
||||
|
||||
if (!chQueries || Object.keys(chQueries).length === 0) {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('chquery_required'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.keys(chQueries).forEach((key) => {
|
||||
if (chQueries[key].rawQuery === '') {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('chquery_required'),
|
||||
});
|
||||
retval = false;
|
||||
}
|
||||
});
|
||||
|
||||
return retval;
|
||||
}, [t, chQueries, queryCategory]);
|
||||
|
||||
const validateQBParams = useCallback((): boolean => {
|
||||
let retval = true;
|
||||
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
|
||||
@ -224,12 +296,17 @@ function FormAlertRules({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateChQueryParams()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return validateQBParams();
|
||||
}, [t, validateQBParams, alertDef, validatePromParams]);
|
||||
}, [t, validateQBParams, validateChQueryParams, alertDef, validatePromParams]);
|
||||
|
||||
const preparePostData = (): AlertDef => {
|
||||
const postableAlert: AlertDef = {
|
||||
...alertDef,
|
||||
alertType,
|
||||
source: window?.location.toString(),
|
||||
ruleType:
|
||||
queryCategory === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
|
||||
@ -238,6 +315,7 @@ function FormAlertRules({
|
||||
compositeMetricQuery: {
|
||||
builderQueries: prepareBuilderQueries(metricQueries, formulaQueries),
|
||||
promQueries,
|
||||
chQueries,
|
||||
queryType: queryCategory,
|
||||
},
|
||||
},
|
||||
@ -251,6 +329,8 @@ function FormAlertRules({
|
||||
metricQueries,
|
||||
formulaQueries,
|
||||
promQueries,
|
||||
chQueries,
|
||||
alertType,
|
||||
]);
|
||||
|
||||
const saveRule = useCallback(async () => {
|
||||
@ -380,6 +460,17 @@ function FormAlertRules({
|
||||
);
|
||||
};
|
||||
|
||||
const renderChQueryChartPreview = (): JSX.Element => {
|
||||
return (
|
||||
<ChartPreview
|
||||
headline={<PlotTag queryType={queryCategory} />}
|
||||
name="Chart Preview"
|
||||
threshold={alertDef.condition?.target}
|
||||
query={manualStagedQuery}
|
||||
userQueryKey={runQueryId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{Element}
|
||||
@ -392,6 +483,7 @@ function FormAlertRules({
|
||||
>
|
||||
{queryCategory === EQueryType.QUERY_BUILDER && renderQBChartPreview()}
|
||||
{queryCategory === EQueryType.PROM && renderPromChartPreview()}
|
||||
{queryCategory === EQueryType.CLICKHOUSE && renderChQueryChartPreview()}
|
||||
<QuerySection
|
||||
queryCategory={queryCategory}
|
||||
setQueryCategory={onQueryCategoryChange}
|
||||
@ -401,6 +493,10 @@ function FormAlertRules({
|
||||
setFormulaQueries={setFormulaQueries}
|
||||
promQueries={promQueries}
|
||||
setPromQueries={setPromQueries}
|
||||
chQueries={chQueries}
|
||||
setChQueries={setChQueries}
|
||||
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
|
||||
runQuery={onRunQuery}
|
||||
/>
|
||||
|
||||
<RuleOptions
|
||||
@ -446,7 +542,12 @@ function FormAlertRules({
|
||||
);
|
||||
}
|
||||
|
||||
FormAlertRules.defaultProps = {
|
||||
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||
};
|
||||
|
||||
interface FormAlertRuleProps {
|
||||
alertType?: AlertTypes;
|
||||
formInstance: FormInstance;
|
||||
initialValue: AlertDef;
|
||||
ruleId: number;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import {
|
||||
IBuilderQueries,
|
||||
IChQueries,
|
||||
IChQuery,
|
||||
IFormulaQueries,
|
||||
IFormulaQuery,
|
||||
IMetricQueries,
|
||||
@ -76,10 +78,12 @@ export const prepareStagedQuery = (
|
||||
m: IMetricQueries,
|
||||
f: IFormulaQueries,
|
||||
p: IPromQueries,
|
||||
c: IChQueries,
|
||||
): IStagedQuery => {
|
||||
const qbList: IMetricQuery[] = [];
|
||||
const formulaList: IFormulaQuery[] = [];
|
||||
const promList: IPromQuery[] = [];
|
||||
const chQueryList: IChQuery[] = [];
|
||||
|
||||
// convert map[string]IMetricQuery to IMetricQuery[]
|
||||
if (m) {
|
||||
@ -101,6 +105,13 @@ export const prepareStagedQuery = (
|
||||
promList.push({ ...p[key], name: key });
|
||||
});
|
||||
}
|
||||
// convert map[string]IChQuery to IChQuery[]
|
||||
if (c) {
|
||||
Object.keys(c).forEach((key) => {
|
||||
console.log('c:', c[key]);
|
||||
chQueryList.push({ ...c[key], name: key, rawQuery: c[key].query });
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
queryType: t,
|
||||
@ -109,7 +120,7 @@ export const prepareStagedQuery = (
|
||||
formulas: formulaList,
|
||||
queryBuilder: qbList,
|
||||
},
|
||||
clickHouse: [],
|
||||
clickHouse: chQueryList,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -27,24 +27,34 @@ function ClickHouseQueryContainer({
|
||||
toggleDisable,
|
||||
toggleDelete,
|
||||
}: IClickHouseQueryHandleChange): void => {
|
||||
const allQueries = queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME];
|
||||
const currentIndexQuery = allQueries[queryIndex];
|
||||
// we must check if queryIndex is number type. because -
|
||||
// ClickHouseQueryBuilder.handleQueryChange has a queryIndex
|
||||
// parameter which supports both number and string formats.
|
||||
// it is because, the dashboard side of query builder has queryIndex as number
|
||||
// while the alert builder uses string format for query index (similar to backend)
|
||||
// hence, this method is only applies when queryIndex is in number format.
|
||||
|
||||
if (rawQuery !== undefined) {
|
||||
currentIndexQuery.rawQuery = rawQuery;
|
||||
}
|
||||
if (typeof queryIndex === 'number') {
|
||||
const allQueries = queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME];
|
||||
|
||||
if (legend !== undefined) {
|
||||
currentIndexQuery.legend = legend;
|
||||
}
|
||||
const currentIndexQuery = allQueries[queryIndex];
|
||||
|
||||
if (toggleDisable) {
|
||||
currentIndexQuery.disabled = !currentIndexQuery.disabled;
|
||||
if (rawQuery !== undefined) {
|
||||
currentIndexQuery.rawQuery = rawQuery;
|
||||
}
|
||||
|
||||
if (legend !== undefined) {
|
||||
currentIndexQuery.legend = legend;
|
||||
}
|
||||
|
||||
if (toggleDisable) {
|
||||
currentIndexQuery.disabled = !currentIndexQuery.disabled;
|
||||
}
|
||||
if (toggleDelete) {
|
||||
allQueries.splice(queryIndex, 1);
|
||||
}
|
||||
updateQueryData({ updatedQuery: { ...queryData } });
|
||||
}
|
||||
if (toggleDelete) {
|
||||
allQueries.splice(queryIndex, 1);
|
||||
}
|
||||
updateQueryData({ updatedQuery: { ...queryData } });
|
||||
};
|
||||
const addQueryHandler = (): void => {
|
||||
queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME].push({
|
||||
|
@ -8,7 +8,7 @@ import { IClickHouseQueryHandleChange } from './types';
|
||||
|
||||
interface IClickHouseQueryBuilderProps {
|
||||
queryData: IClickHouseQuery;
|
||||
queryIndex: number;
|
||||
queryIndex: number | string;
|
||||
handleQueryChange: (args: IClickHouseQueryHandleChange) => void;
|
||||
}
|
||||
|
||||
@ -43,6 +43,9 @@ function ClickHouseQueryBuilder({
|
||||
scrollbar: {
|
||||
alwaysConsumeMouseWheel: false,
|
||||
},
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
|
||||
|
||||
export interface IClickHouseQueryHandleChange {
|
||||
queryIndex: number;
|
||||
queryIndex: number | string;
|
||||
rawQuery?: IClickHouseQuery['rawQuery'];
|
||||
legend?: IClickHouseQuery['legend'];
|
||||
toggleDisable?: IClickHouseQuery['disabled'];
|
||||
|
@ -1,9 +1,8 @@
|
||||
import CreateAlertRule from 'container/CreateAlertRule';
|
||||
import React from 'react';
|
||||
import { alertDefaults } from 'types/api/alerts/create';
|
||||
|
||||
function CreateAlertPage(): JSX.Element {
|
||||
return <CreateAlertRule initialValue={alertDefaults} />;
|
||||
return <CreateAlertRule />;
|
||||
}
|
||||
|
||||
export default CreateAlertPage;
|
||||
|
7
frontend/src/types/api/alerts/alertTypes.ts
Normal file
7
frontend/src/types/api/alerts/alertTypes.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// this list must exactly match with the backend
|
||||
export enum AlertTypes {
|
||||
NONE = 'NONE',
|
||||
METRICS_BASED_ALERT = 'METRIC_BASED_ALERT',
|
||||
LOGS_BASED_ALERT = 'LOGS_BASED_ALERT',
|
||||
TRACES_BASED_ALERT = 'TRACES_BASED_ALERT',
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
IClickHouseQuery,
|
||||
IMetricsBuilderFormula,
|
||||
IMetricsBuilderQuery,
|
||||
IPromQLQuery,
|
||||
@ -9,17 +10,25 @@ import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
|
||||
export interface ICompositeMetricQuery {
|
||||
builderQueries: IBuilderQueries;
|
||||
promQueries: IPromQueries;
|
||||
chQueries: IChQueries;
|
||||
queryType: EQueryType;
|
||||
}
|
||||
|
||||
export interface IPromQueries {
|
||||
[key: string]: IPromQuery;
|
||||
export interface IChQueries {
|
||||
[key: string]: IChQuery;
|
||||
}
|
||||
|
||||
export interface IChQuery extends IClickHouseQuery {
|
||||
query: string;
|
||||
}
|
||||
|
||||
export interface IPromQuery extends IPromQLQuery {
|
||||
stats?: '';
|
||||
}
|
||||
|
||||
export interface IPromQueries {
|
||||
[key: string]: IPromQuery;
|
||||
}
|
||||
export interface IBuilderQueries {
|
||||
[key: string]: IBuilderQuery;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
|
||||
import { defaultCompareOp, defaultEvalWindow, defaultMatchType } from './def';
|
||||
|
||||
export interface Props {
|
||||
data: AlertDef;
|
||||
}
|
||||
@ -10,39 +8,3 @@ export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export const alertDefaults: AlertDef = {
|
||||
condition: {
|
||||
compositeMetricQuery: {
|
||||
builderQueries: {
|
||||
A: {
|
||||
queryName: 'A',
|
||||
name: 'A',
|
||||
formulaOnly: false,
|
||||
metricName: '',
|
||||
tagFilters: {
|
||||
op: 'AND',
|
||||
items: [],
|
||||
},
|
||||
groupBy: [],
|
||||
aggregateOperator: 1,
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
toggleDisable: false,
|
||||
toggleDelete: false,
|
||||
},
|
||||
},
|
||||
promQueries: {},
|
||||
queryType: 1,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: defaultMatchType,
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
},
|
||||
annotations: {
|
||||
description: 'A new alert',
|
||||
},
|
||||
evalWindow: defaultEvalWindow,
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ export const defaultCompareOp = '1';
|
||||
|
||||
export interface AlertDef {
|
||||
id?: number;
|
||||
alertType?: string;
|
||||
alert?: string;
|
||||
ruleType?: string;
|
||||
condition: RuleCondition;
|
||||
|
Loading…
x
Reference in New Issue
Block a user