mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 14:45:58 +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",
|
"condition_required": "at least one metric condition is required",
|
||||||
"alertname_required": "alert name is required",
|
"alertname_required": "alert name is required",
|
||||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
"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_savechanges": "Save Rule",
|
||||||
"button_createrule": "Create Rule",
|
"button_createrule": "Create Rule",
|
||||||
"button_returntorules": "Return to rules",
|
"button_returntorules": "Return to rules",
|
||||||
@ -55,6 +56,7 @@
|
|||||||
"button_formula": "Formula",
|
"button_formula": "Formula",
|
||||||
"tab_qb": "Query Builder",
|
"tab_qb": "Query Builder",
|
||||||
"tab_promql": "PromQL",
|
"tab_promql": "PromQL",
|
||||||
|
"tab_chquery": "ClickHouse Query",
|
||||||
"title_confirm": "Confirm",
|
"title_confirm": "Confirm",
|
||||||
"button_ok": "Yes",
|
"button_ok": "Yes",
|
||||||
"button_cancel": "No",
|
"button_cancel": "No",
|
||||||
@ -88,5 +90,21 @@
|
|||||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
"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_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",
|
"condition_required": "at least one metric condition is required",
|
||||||
"alertname_required": "alert name is required",
|
"alertname_required": "alert name is required",
|
||||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
"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_savechanges": "Save Rule",
|
||||||
"button_createrule": "Create Rule",
|
"button_createrule": "Create Rule",
|
||||||
"button_returntorules": "Return to rules",
|
"button_returntorules": "Return to rules",
|
||||||
@ -55,6 +56,7 @@
|
|||||||
"button_formula": "Formula",
|
"button_formula": "Formula",
|
||||||
"tab_qb": "Query Builder",
|
"tab_qb": "Query Builder",
|
||||||
"tab_promql": "PromQL",
|
"tab_promql": "PromQL",
|
||||||
|
"tab_chquery": "ClickHouse Query",
|
||||||
"title_confirm": "Confirm",
|
"title_confirm": "Confirm",
|
||||||
"button_ok": "Yes",
|
"button_ok": "Yes",
|
||||||
"button_cancel": "No",
|
"button_cancel": "No",
|
||||||
@ -88,5 +90,21 @@
|
|||||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
"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_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 FormAlertRules from 'container/FormAlertRules';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { AlertDef } from 'types/api/alerts/def';
|
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 [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 (
|
return (
|
||||||
<FormAlertRules
|
<FormAlertRules
|
||||||
|
alertType={alertType}
|
||||||
formInstance={formInstance}
|
formInstance={formInstance}
|
||||||
initialValue={initialValue}
|
initialValue={initValues}
|
||||||
ruleId={0}
|
ruleId={0}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateRulesProps {
|
|
||||||
initialValue: AlertDef;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreateRules;
|
export default CreateRules;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import FormAlertRules from 'container/FormAlertRules';
|
import FormAlertRules from 'container/FormAlertRules';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import { AlertDef } from 'types/api/alerts/def';
|
import { AlertDef } from 'types/api/alerts/def';
|
||||||
|
|
||||||
function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
||||||
@ -8,6 +9,11 @@ function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormAlertRules
|
<FormAlertRules
|
||||||
|
alertType={
|
||||||
|
initialValue.alertType
|
||||||
|
? (initialValue.alertType as AlertTypes)
|
||||||
|
: AlertTypes.METRICS_BASED_ALERT
|
||||||
|
}
|
||||||
formInstance={formInstance}
|
formInstance={formInstance}
|
||||||
initialValue={initialValue}
|
initialValue={initialValue}
|
||||||
ruleId={ruleId}
|
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 { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
import { StaticLineProps } from 'components/Graph';
|
import { StaticLineProps } from 'components/Graph';
|
||||||
|
import Spinner from 'components/Spinner';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
import GridGraphComponent from 'container/GridGraphComponent';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
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 getChartData from 'lib/getChartData';
|
import getChartData from 'lib/getChartData';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
||||||
@ -22,6 +23,10 @@ export interface ChartPreviewProps {
|
|||||||
selectedInterval?: Time;
|
selectedInterval?: Time;
|
||||||
headline?: JSX.Element;
|
headline?: JSX.Element;
|
||||||
threshold?: number | undefined;
|
threshold?: number | undefined;
|
||||||
|
userQueryKey?: string;
|
||||||
|
}
|
||||||
|
interface QueryResponseError {
|
||||||
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChartPreview({
|
function ChartPreview({
|
||||||
@ -32,6 +37,7 @@ function ChartPreview({
|
|||||||
selectedInterval = '5min',
|
selectedInterval = '5min',
|
||||||
headline,
|
headline,
|
||||||
threshold,
|
threshold,
|
||||||
|
userQueryKey,
|
||||||
}: ChartPreviewProps): JSX.Element | null {
|
}: ChartPreviewProps): JSX.Element | null {
|
||||||
const { t } = useTranslation('alerts');
|
const { t } = useTranslation('alerts');
|
||||||
const staticLine: StaticLineProps | undefined =
|
const staticLine: StaticLineProps | undefined =
|
||||||
@ -46,9 +52,34 @@ function ChartPreview({
|
|||||||
}
|
}
|
||||||
: undefined;
|
: 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({
|
const queryResponse = useQuery({
|
||||||
queryKey: ['chartPreview', queryKey, selectedInterval],
|
queryKey: [
|
||||||
|
'chartPreview',
|
||||||
|
userQueryKey || JSON.stringify(query),
|
||||||
|
selectedInterval,
|
||||||
|
],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
GetMetricQueryRange({
|
GetMetricQueryRange({
|
||||||
query: query || {
|
query: query || {
|
||||||
@ -64,14 +95,8 @@ function ChartPreview({
|
|||||||
graphType,
|
graphType,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
}),
|
}),
|
||||||
enabled:
|
retry: false,
|
||||||
query != null &&
|
enabled: canQuery,
|
||||||
((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 !== '')),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartDataSet = queryResponse.isError
|
const chartDataSet = queryResponse.isError
|
||||||
@ -89,15 +114,14 @@ function ChartPreview({
|
|||||||
return (
|
return (
|
||||||
<ChartContainer>
|
<ChartContainer>
|
||||||
{headline}
|
{headline}
|
||||||
{(queryResponse?.data?.error || queryResponse?.isError) && (
|
{(queryResponse?.isError || queryResponse?.error) && (
|
||||||
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
||||||
<InfoCircleOutlined />{' '}
|
<InfoCircleOutlined />{' '}
|
||||||
{queryResponse?.data?.error ||
|
{(queryResponse?.error as QueryResponseError).message ||
|
||||||
queryResponse?.error ||
|
|
||||||
t('preview_chart_unexpected_error')}
|
t('preview_chart_unexpected_error')}
|
||||||
</FailedMessageContainer>
|
</FailedMessageContainer>
|
||||||
)}
|
)}
|
||||||
|
{queryResponse.isLoading && <Spinner size="large" tip="Loading..." />}
|
||||||
{chartDataSet && !queryResponse.isError && (
|
{chartDataSet && !queryResponse.isError && (
|
||||||
<GridGraphComponent
|
<GridGraphComponent
|
||||||
title={name}
|
title={name}
|
||||||
@ -118,6 +142,7 @@ ChartPreview.defaultProps = {
|
|||||||
selectedInterval: '5min',
|
selectedInterval: '5min',
|
||||||
headline: undefined,
|
headline: undefined,
|
||||||
threshold: undefined,
|
threshold: undefined,
|
||||||
|
userQueryKey: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChartPreview;
|
export default ChartPreview;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { red } from '@ant-design/colors';
|
||||||
import { Card, Tooltip } from 'antd';
|
import { Card, Tooltip } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@ -10,7 +11,8 @@ export const NotFoundContainer = styled.div`
|
|||||||
|
|
||||||
export const FailedMessageContainer = styled(Tooltip)`
|
export const FailedMessageContainer = styled(Tooltip)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
color: ${red};
|
||||||
|
top: 4rem;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
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 MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula';
|
||||||
import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query';
|
import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query';
|
||||||
import {
|
import {
|
||||||
@ -8,13 +8,16 @@ import {
|
|||||||
} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
|
} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import {
|
import {
|
||||||
|
IChQueries,
|
||||||
IFormulaQueries,
|
IFormulaQueries,
|
||||||
IMetricQueries,
|
IMetricQueries,
|
||||||
IPromQueries,
|
IPromQueries,
|
||||||
} from 'types/api/alerts/compositeQuery';
|
} from 'types/api/alerts/compositeQuery';
|
||||||
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
|
import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
|
||||||
|
|
||||||
|
import ChQuerySection from './ChQuerySection';
|
||||||
import PromqlSection from './PromqlSection';
|
import PromqlSection from './PromqlSection';
|
||||||
import { FormContainer, QueryButton, StepHeading } from './styles';
|
import { FormContainer, QueryButton, StepHeading } from './styles';
|
||||||
import { toIMetricsBuilderQuery } from './utils';
|
import { toIMetricsBuilderQuery } from './utils';
|
||||||
@ -29,6 +32,10 @@ function QuerySection({
|
|||||||
setFormulaQueries,
|
setFormulaQueries,
|
||||||
promQueries,
|
promQueries,
|
||||||
setPromQueries,
|
setPromQueries,
|
||||||
|
chQueries,
|
||||||
|
setChQueries,
|
||||||
|
alertType,
|
||||||
|
runQuery,
|
||||||
}: QuerySectionProps): JSX.Element {
|
}: QuerySectionProps): JSX.Element {
|
||||||
// init namespace for translations
|
// init namespace for translations
|
||||||
const { t } = useTranslation('alerts');
|
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));
|
setQueryCategory(parseInt(s, 10));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -196,6 +217,10 @@ function QuerySection({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderChQueryUI = (): JSX.Element => {
|
||||||
|
return <ChQuerySection chQueries={chQueries} setChQueries={setChQueries} />;
|
||||||
|
};
|
||||||
|
|
||||||
const renderFormulaButton = (): JSX.Element => {
|
const renderFormulaButton = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<QueryButton onClick={addFormula} icon={<PlusOutlined />}>
|
<QueryButton onClick={addFormula} icon={<PlusOutlined />}>
|
||||||
@ -258,23 +283,84 @@ function QuerySection({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
|
||||||
<>
|
const handleRunQuery = (): void => {
|
||||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
runQuery();
|
||||||
<FormContainer>
|
};
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
|
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
|
<Tabs
|
||||||
type="card"
|
type="card"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
defaultActiveKey={EQueryType.QUERY_BUILDER.toString()}
|
defaultActiveKey={EQueryType.QUERY_BUILDER.toString()}
|
||||||
activeKey={queryCategory.toString()}
|
activeKey={queryCategory.toString()}
|
||||||
onChange={handleQueryCategoryChange}
|
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_qb')} key={EQueryType.QUERY_BUILDER.toString()} />
|
||||||
|
<TabPane tab={t('tab_chquery')} key={EQueryType.CLICKHOUSE.toString()} />
|
||||||
<TabPane tab={t('tab_promql')} key={EQueryType.PROM.toString()} />
|
<TabPane tab={t('tab_promql')} key={EQueryType.PROM.toString()} />
|
||||||
</Tabs>
|
</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>
|
</FormContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -289,6 +375,10 @@ interface QuerySectionProps {
|
|||||||
setFormulaQueries: (b: IFormulaQueries) => void;
|
setFormulaQueries: (b: IFormulaQueries) => void;
|
||||||
promQueries: IPromQueries;
|
promQueries: IPromQueries;
|
||||||
setPromQueries: (p: IPromQueries) => void;
|
setPromQueries: (p: IPromQueries) => void;
|
||||||
|
chQueries: IChQueries;
|
||||||
|
setChQueries: (q: IChQueries) => void;
|
||||||
|
alertType: AlertTypes;
|
||||||
|
runQuery: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QuerySection;
|
export default QuerySection;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Col, Row, Typography } from 'antd';
|
import { Col, Row, Typography } from 'antd';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
|
||||||
import {
|
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 (
|
return (
|
||||||
<StyledMainContainer>
|
<StyledMainContainer>
|
||||||
<Row>
|
<Row>
|
||||||
@ -121,6 +178,7 @@ function UserGuide({ queryType }: UserGuideProps): JSX.Element {
|
|||||||
</Row>
|
</Row>
|
||||||
{queryType === EQueryType.QUERY_BUILDER && renderGuideForQB()}
|
{queryType === EQueryType.QUERY_BUILDER && renderGuideForQB()}
|
||||||
{queryType === EQueryType.PROM && renderGuideForPQL()}
|
{queryType === EQueryType.PROM && renderGuideForPQL()}
|
||||||
|
{queryType === EQueryType.CLICKHOUSE && renderGuideForCH()}
|
||||||
</StyledMainContainer>
|
</StyledMainContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@ import history from 'lib/history';
|
|||||||
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 {
|
import {
|
||||||
|
IChQueries,
|
||||||
IFormulaQueries,
|
IFormulaQueries,
|
||||||
IMetricQueries,
|
IMetricQueries,
|
||||||
IPromQueries,
|
IPromQueries,
|
||||||
@ -45,6 +47,7 @@ import {
|
|||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
function FormAlertRules({
|
function FormAlertRules({
|
||||||
|
alertType,
|
||||||
formInstance,
|
formInstance,
|
||||||
initialValue,
|
initialValue,
|
||||||
ruleId,
|
ruleId,
|
||||||
@ -57,6 +60,10 @@ function FormAlertRules({
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@ -82,9 +89,31 @@ function FormAlertRules({
|
|||||||
...initQuery?.promQueries,
|
...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 [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
|
// this use effect initiates staged query and
|
||||||
// other queries based on server data.
|
// other queries based on server data.
|
||||||
@ -101,14 +130,26 @@ function FormAlertRules({
|
|||||||
const fq = toFormulaQueries(initQuery?.builderQueries);
|
const fq = toFormulaQueries(initQuery?.builderQueries);
|
||||||
|
|
||||||
// prepare staged query
|
// 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 pq = initQuery?.promQueries;
|
||||||
|
const chq = initQuery?.chQueries;
|
||||||
|
|
||||||
setQueryCategory(typ);
|
setQueryCategory(typ);
|
||||||
setMetricQueries(mq);
|
setMetricQueries(mq);
|
||||||
setFormulaQueries(fq);
|
setFormulaQueries(fq);
|
||||||
setPromQueries(pq);
|
setPromQueries(pq);
|
||||||
setStagedQuery(sq);
|
setStagedQuery(sq);
|
||||||
|
|
||||||
|
// also set manually staged query
|
||||||
|
setManualStagedQuery(sq);
|
||||||
|
|
||||||
|
setChQueries(chq);
|
||||||
setAlertDef(initialValue);
|
setAlertDef(initialValue);
|
||||||
}, [initialValue]);
|
}, [initialValue]);
|
||||||
|
|
||||||
@ -121,9 +162,15 @@ function FormAlertRules({
|
|||||||
metricQueries,
|
metricQueries,
|
||||||
formulaQueries,
|
formulaQueries,
|
||||||
promQueries,
|
promQueries,
|
||||||
|
chQueries,
|
||||||
);
|
);
|
||||||
setStagedQuery(sq);
|
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(() => {
|
const onCancelHandler = useCallback(() => {
|
||||||
history.replace(ROUTES.LIST_ALL_ALERT);
|
history.replace(ROUTES.LIST_ALL_ALERT);
|
||||||
@ -169,6 +216,31 @@ function FormAlertRules({
|
|||||||
return retval;
|
return retval;
|
||||||
}, [t, promQueries, queryCategory]);
|
}, [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 => {
|
const validateQBParams = useCallback((): boolean => {
|
||||||
let retval = true;
|
let retval = true;
|
||||||
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
|
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
|
||||||
@ -224,12 +296,17 @@ function FormAlertRules({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validateChQueryParams()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return validateQBParams();
|
return validateQBParams();
|
||||||
}, [t, validateQBParams, alertDef, validatePromParams]);
|
}, [t, validateQBParams, validateChQueryParams, alertDef, validatePromParams]);
|
||||||
|
|
||||||
const preparePostData = (): AlertDef => {
|
const preparePostData = (): AlertDef => {
|
||||||
const postableAlert: AlertDef = {
|
const postableAlert: AlertDef = {
|
||||||
...alertDef,
|
...alertDef,
|
||||||
|
alertType,
|
||||||
source: window?.location.toString(),
|
source: window?.location.toString(),
|
||||||
ruleType:
|
ruleType:
|
||||||
queryCategory === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
|
queryCategory === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
|
||||||
@ -238,6 +315,7 @@ function FormAlertRules({
|
|||||||
compositeMetricQuery: {
|
compositeMetricQuery: {
|
||||||
builderQueries: prepareBuilderQueries(metricQueries, formulaQueries),
|
builderQueries: prepareBuilderQueries(metricQueries, formulaQueries),
|
||||||
promQueries,
|
promQueries,
|
||||||
|
chQueries,
|
||||||
queryType: queryCategory,
|
queryType: queryCategory,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -251,6 +329,8 @@ function FormAlertRules({
|
|||||||
metricQueries,
|
metricQueries,
|
||||||
formulaQueries,
|
formulaQueries,
|
||||||
promQueries,
|
promQueries,
|
||||||
|
chQueries,
|
||||||
|
alertType,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const saveRule = useCallback(async () => {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{Element}
|
{Element}
|
||||||
@ -392,6 +483,7 @@ function FormAlertRules({
|
|||||||
>
|
>
|
||||||
{queryCategory === EQueryType.QUERY_BUILDER && renderQBChartPreview()}
|
{queryCategory === EQueryType.QUERY_BUILDER && renderQBChartPreview()}
|
||||||
{queryCategory === EQueryType.PROM && renderPromChartPreview()}
|
{queryCategory === EQueryType.PROM && renderPromChartPreview()}
|
||||||
|
{queryCategory === EQueryType.CLICKHOUSE && renderChQueryChartPreview()}
|
||||||
<QuerySection
|
<QuerySection
|
||||||
queryCategory={queryCategory}
|
queryCategory={queryCategory}
|
||||||
setQueryCategory={onQueryCategoryChange}
|
setQueryCategory={onQueryCategoryChange}
|
||||||
@ -401,6 +493,10 @@ function FormAlertRules({
|
|||||||
setFormulaQueries={setFormulaQueries}
|
setFormulaQueries={setFormulaQueries}
|
||||||
promQueries={promQueries}
|
promQueries={promQueries}
|
||||||
setPromQueries={setPromQueries}
|
setPromQueries={setPromQueries}
|
||||||
|
chQueries={chQueries}
|
||||||
|
setChQueries={setChQueries}
|
||||||
|
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
|
||||||
|
runQuery={onRunQuery}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RuleOptions
|
<RuleOptions
|
||||||
@ -446,7 +542,12 @@ function FormAlertRules({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormAlertRules.defaultProps = {
|
||||||
|
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||||
|
};
|
||||||
|
|
||||||
interface FormAlertRuleProps {
|
interface FormAlertRuleProps {
|
||||||
|
alertType?: AlertTypes;
|
||||||
formInstance: FormInstance;
|
formInstance: FormInstance;
|
||||||
initialValue: AlertDef;
|
initialValue: AlertDef;
|
||||||
ruleId: number;
|
ruleId: number;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||||
import {
|
import {
|
||||||
IBuilderQueries,
|
IBuilderQueries,
|
||||||
|
IChQueries,
|
||||||
|
IChQuery,
|
||||||
IFormulaQueries,
|
IFormulaQueries,
|
||||||
IFormulaQuery,
|
IFormulaQuery,
|
||||||
IMetricQueries,
|
IMetricQueries,
|
||||||
@ -76,10 +78,12 @@ export const prepareStagedQuery = (
|
|||||||
m: IMetricQueries,
|
m: IMetricQueries,
|
||||||
f: IFormulaQueries,
|
f: IFormulaQueries,
|
||||||
p: IPromQueries,
|
p: IPromQueries,
|
||||||
|
c: IChQueries,
|
||||||
): IStagedQuery => {
|
): IStagedQuery => {
|
||||||
const qbList: IMetricQuery[] = [];
|
const qbList: IMetricQuery[] = [];
|
||||||
const formulaList: IFormulaQuery[] = [];
|
const formulaList: IFormulaQuery[] = [];
|
||||||
const promList: IPromQuery[] = [];
|
const promList: IPromQuery[] = [];
|
||||||
|
const chQueryList: IChQuery[] = [];
|
||||||
|
|
||||||
// convert map[string]IMetricQuery to IMetricQuery[]
|
// convert map[string]IMetricQuery to IMetricQuery[]
|
||||||
if (m) {
|
if (m) {
|
||||||
@ -101,6 +105,13 @@ export const prepareStagedQuery = (
|
|||||||
promList.push({ ...p[key], name: key });
|
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 {
|
return {
|
||||||
queryType: t,
|
queryType: t,
|
||||||
@ -109,7 +120,7 @@ export const prepareStagedQuery = (
|
|||||||
formulas: formulaList,
|
formulas: formulaList,
|
||||||
queryBuilder: qbList,
|
queryBuilder: qbList,
|
||||||
},
|
},
|
||||||
clickHouse: [],
|
clickHouse: chQueryList,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,24 +27,34 @@ function ClickHouseQueryContainer({
|
|||||||
toggleDisable,
|
toggleDisable,
|
||||||
toggleDelete,
|
toggleDelete,
|
||||||
}: IClickHouseQueryHandleChange): void => {
|
}: IClickHouseQueryHandleChange): void => {
|
||||||
const allQueries = queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME];
|
// we must check if queryIndex is number type. because -
|
||||||
const currentIndexQuery = allQueries[queryIndex];
|
// 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) {
|
if (typeof queryIndex === 'number') {
|
||||||
currentIndexQuery.rawQuery = rawQuery;
|
const allQueries = queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME];
|
||||||
}
|
|
||||||
|
|
||||||
if (legend !== undefined) {
|
const currentIndexQuery = allQueries[queryIndex];
|
||||||
currentIndexQuery.legend = legend;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toggleDisable) {
|
if (rawQuery !== undefined) {
|
||||||
currentIndexQuery.disabled = !currentIndexQuery.disabled;
|
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 => {
|
const addQueryHandler = (): void => {
|
||||||
queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME].push({
|
queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME].push({
|
||||||
|
@ -8,7 +8,7 @@ import { IClickHouseQueryHandleChange } from './types';
|
|||||||
|
|
||||||
interface IClickHouseQueryBuilderProps {
|
interface IClickHouseQueryBuilderProps {
|
||||||
queryData: IClickHouseQuery;
|
queryData: IClickHouseQuery;
|
||||||
queryIndex: number;
|
queryIndex: number | string;
|
||||||
handleQueryChange: (args: IClickHouseQueryHandleChange) => void;
|
handleQueryChange: (args: IClickHouseQueryHandleChange) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +43,9 @@ function ClickHouseQueryBuilder({
|
|||||||
scrollbar: {
|
scrollbar: {
|
||||||
alwaysConsumeMouseWheel: false,
|
alwaysConsumeMouseWheel: false,
|
||||||
},
|
},
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
|
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export interface IClickHouseQueryHandleChange {
|
export interface IClickHouseQueryHandleChange {
|
||||||
queryIndex: number;
|
queryIndex: number | string;
|
||||||
rawQuery?: IClickHouseQuery['rawQuery'];
|
rawQuery?: IClickHouseQuery['rawQuery'];
|
||||||
legend?: IClickHouseQuery['legend'];
|
legend?: IClickHouseQuery['legend'];
|
||||||
toggleDisable?: IClickHouseQuery['disabled'];
|
toggleDisable?: IClickHouseQuery['disabled'];
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import CreateAlertRule from 'container/CreateAlertRule';
|
import CreateAlertRule from 'container/CreateAlertRule';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { alertDefaults } from 'types/api/alerts/create';
|
|
||||||
|
|
||||||
function CreateAlertPage(): JSX.Element {
|
function CreateAlertPage(): JSX.Element {
|
||||||
return <CreateAlertRule initialValue={alertDefaults} />;
|
return <CreateAlertRule />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateAlertPage;
|
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 {
|
import {
|
||||||
|
IClickHouseQuery,
|
||||||
IMetricsBuilderFormula,
|
IMetricsBuilderFormula,
|
||||||
IMetricsBuilderQuery,
|
IMetricsBuilderQuery,
|
||||||
IPromQLQuery,
|
IPromQLQuery,
|
||||||
@ -9,17 +10,25 @@ import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
|
|||||||
export interface ICompositeMetricQuery {
|
export interface ICompositeMetricQuery {
|
||||||
builderQueries: IBuilderQueries;
|
builderQueries: IBuilderQueries;
|
||||||
promQueries: IPromQueries;
|
promQueries: IPromQueries;
|
||||||
|
chQueries: IChQueries;
|
||||||
queryType: EQueryType;
|
queryType: EQueryType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPromQueries {
|
export interface IChQueries {
|
||||||
[key: string]: IPromQuery;
|
[key: string]: IChQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChQuery extends IClickHouseQuery {
|
||||||
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPromQuery extends IPromQLQuery {
|
export interface IPromQuery extends IPromQLQuery {
|
||||||
stats?: '';
|
stats?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPromQueries {
|
||||||
|
[key: string]: IPromQuery;
|
||||||
|
}
|
||||||
export interface IBuilderQueries {
|
export interface IBuilderQueries {
|
||||||
[key: string]: IBuilderQuery;
|
[key: string]: IBuilderQuery;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { AlertDef } from 'types/api/alerts/def';
|
import { AlertDef } from 'types/api/alerts/def';
|
||||||
|
|
||||||
import { defaultCompareOp, defaultEvalWindow, defaultMatchType } from './def';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
data: AlertDef;
|
data: AlertDef;
|
||||||
}
|
}
|
||||||
@ -10,39 +8,3 @@ export interface PayloadProps {
|
|||||||
status: string;
|
status: string;
|
||||||
data: 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 {
|
export interface AlertDef {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
alertType?: string;
|
||||||
alert?: string;
|
alert?: string;
|
||||||
ruleType?: string;
|
ruleType?: string;
|
||||||
condition: RuleCondition;
|
condition: RuleCondition;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user