feat(FE): dashboard alerts is added (#3908)

* feat: create menu items is added in the service application widgets

* chore: filter query is updated

* fix: build is fixed

* feat: selected query is updated

* chore: create alerts is updated

* feat: dashboard alerts is updated

* chore: spacing is updated

* feat: dashboard to alerts is updated

* fix: build is fixed

* feat: alert query options is updated

* chore: menu list is updated for tabel panel

---------

Co-authored-by: Rajat Dabade <rajat@signoz.io>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
Palash Gupta 2023-11-13 13:54:31 +05:30 committed by GitHub
parent 758013d7cd
commit 5a9f626da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 377 additions and 217 deletions

View File

@ -34,7 +34,7 @@
"button_returntorules": "Return to rules", "button_returntorules": "Return to rules",
"button_cancelchanges": "Cancel", "button_cancelchanges": "Cancel",
"button_discard": "Discard", "button_discard": "Discard",
"text_condition1": "Send a notification when the metric is", "text_condition1": "Send a notification when",
"text_condition2": "the threshold", "text_condition2": "the threshold",
"text_condition3": "during the last", "text_condition3": "during the last",
"option_5min": "5 mins", "option_5min": "5 mins",
@ -109,5 +109,6 @@
"traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.", "traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.",
"exceptions_based_alert": "Exceptions-based Alert", "exceptions_based_alert": "Exceptions-based Alert",
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.", "exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
"field_unit": "Threshold unit" "field_unit": "Threshold unit",
"selected_query_placeholder": "Select query"
} }

View File

@ -1,85 +1,85 @@
{ {
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.", "preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
"preview_chart_threshold_label": "Threshold", "preview_chart_threshold_label": "Threshold",
"placeholder_label_key_pair": "Click here to enter a label (key value pairs)", "placeholder_label_key_pair": "Click here to enter a label (key value pairs)",
"button_yes": "Yes", "button_yes": "Yes",
"button_no": "No", "button_no": "No",
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?", "remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
"remove_label_success": "Labels cleared", "remove_label_success": "Labels cleared",
"alert_form_step1": "Step 1 - Define the metric", "alert_form_step1": "Step 1 - Define the metric",
"alert_form_step2": "Step 2 - Define Alert Conditions", "alert_form_step2": "Step 2 - Define Alert Conditions",
"alert_form_step3": "Step 3 - Alert Configuration", "alert_form_step3": "Step 3 - Alert Configuration",
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries", "metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
"confirm_save_title": "Save Changes", "confirm_save_title": "Save Changes",
"confirm_save_content_part1": "Your alert built with", "confirm_save_content_part1": "Your alert built with",
"confirm_save_content_part2": "query will be saved. Press OK to confirm.", "confirm_save_content_part2": "query will be saved. Press OK to confirm.",
"unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin", "unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin",
"rule_created": "Rule created successfully", "rule_created": "Rule created successfully",
"rule_edited": "Rule edited successfully", "rule_edited": "Rule edited successfully",
"expression_missing": "expression is missing in {{where}}", "expression_missing": "expression is missing in {{where}}",
"metricname_missing": "metric name is missing in {{where}}", "metricname_missing": "metric name is missing in {{where}}",
"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",
"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",
"button_cancelchanges": "Cancel", "button_cancelchanges": "Cancel",
"button_discard": "Discard", "button_discard": "Discard",
"text_condition1": "Send a notification when the metric is", "text_condition1": "Send a notification when",
"text_condition2": "the threshold", "text_condition2": "the threshold",
"text_condition3": "during the last", "text_condition3": "during the last",
"option_5min": "5 mins", "option_5min": "5 mins",
"option_10min": "10 mins", "option_10min": "10 mins",
"option_15min": "15 mins", "option_15min": "15 mins",
"option_60min": "60 mins", "option_60min": "60 mins",
"option_4hours": "4 hours", "option_4hours": "4 hours",
"option_24hours": "24 hours", "option_24hours": "24 hours",
"field_threshold": "Alert Threshold", "field_threshold": "Alert Threshold",
"option_allthetimes": "all the times", "option_allthetimes": "all the times",
"option_atleastonce": "at least once", "option_atleastonce": "at least once",
"option_onaverage": "on average", "option_onaverage": "on average",
"option_intotal": "in total", "option_intotal": "in total",
"option_above": "above", "option_above": "above",
"option_below": "below", "option_below": "below",
"option_equal": "is equal to", "option_equal": "is equal to",
"option_notequal": "not equal to", "option_notequal": "not equal to",
"button_query": "Query", "button_query": "Query",
"button_formula": "Formula", "button_formula": "Formula",
"tab_qb": "Query Builder", "tab_qb": "Query Builder",
"tab_promql": "PromQL", "tab_promql": "PromQL",
"title_confirm": "Confirm", "title_confirm": "Confirm",
"button_ok": "Yes", "button_ok": "Yes",
"button_cancel": "No", "button_cancel": "No",
"field_promql_expr": "PromQL Expression", "field_promql_expr": "PromQL Expression",
"field_alert_name": "Alert Name", "field_alert_name": "Alert Name",
"field_alert_desc": "Alert Description", "field_alert_desc": "Alert Description",
"field_labels": "Labels", "field_labels": "Labels",
"field_severity": "Severity", "field_severity": "Severity",
"option_critical": "Critical", "option_critical": "Critical",
"option_error": "Error", "option_error": "Error",
"option_warning": "Warning", "option_warning": "Warning",
"option_info": "Info", "option_info": "Info",
"user_guide_headline": "Steps to create an Alert", "user_guide_headline": "Steps to create an Alert",
"user_guide_qb_step1": "Step 1 - Define the metric", "user_guide_qb_step1": "Step 1 - Define the metric",
"user_guide_qb_step1a": "Choose a metric which you want to create an alert on", "user_guide_qb_step1a": "Choose a metric which you want to create an alert on",
"user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed", "user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed",
"user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric", "user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric",
"user_guide_qb_step1d": "Create a formula based on Queries if needed", "user_guide_qb_step1d": "Create a formula based on Queries if needed",
"user_guide_qb_step2": "Step 2 - Define Alert Conditions", "user_guide_qb_step2": "Step 2 - Define Alert Conditions",
"user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value", "user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value",
"user_guide_qb_step2b": "Enter the Alert threshold", "user_guide_qb_step2b": "Enter the Alert threshold",
"user_guide_qb_step3": "Step 3 -Alert Configuration", "user_guide_qb_step3": "Step 3 -Alert Configuration",
"user_guide_qb_step3a": "Set alert severity, name and descriptions", "user_guide_qb_step3a": "Set alert severity, name and descriptions",
"user_guide_qb_step3b": "Add tags to the alert in the Label field if needed", "user_guide_qb_step3b": "Add tags to the alert in the Label field if needed",
"user_guide_pql_step1": "Step 1 - Define the metric", "user_guide_pql_step1": "Step 1 - Define the metric",
"user_guide_pql_step1a": "Write a PromQL query for the metric", "user_guide_pql_step1a": "Write a PromQL query for the metric",
"user_guide_pql_step1b": "Format the legends based on labels you want to highlight", "user_guide_pql_step1b": "Format the legends based on labels you want to highlight",
"user_guide_pql_step2": "Step 2 - Define Alert Conditions", "user_guide_pql_step2": "Step 2 - Define Alert Conditions",
"user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value", "user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value",
"user_guide_pql_step2b": "Enter the Alert threshold", "user_guide_pql_step2b": "Enter the Alert threshold",
"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_tooltip_more_help": "More details on how to create alerts"
} }

View File

@ -34,7 +34,7 @@
"button_returntorules": "Return to rules", "button_returntorules": "Return to rules",
"button_cancelchanges": "Cancel", "button_cancelchanges": "Cancel",
"button_discard": "Discard", "button_discard": "Discard",
"text_condition1": "Send a notification when the metric is", "text_condition1": "Send a notification when",
"text_condition2": "the threshold", "text_condition2": "the threshold",
"text_condition3": "during the last", "text_condition3": "during the last",
"option_5min": "5 mins", "option_5min": "5 mins",
@ -109,5 +109,6 @@
"traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.", "traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.",
"exceptions_based_alert": "Exceptions-based Alert", "exceptions_based_alert": "Exceptions-based Alert",
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.", "exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
"field_unit": "Threshold unit" "field_unit": "Threshold unit",
"selected_query_placeholder": "Select query"
} }

View File

@ -1,85 +1,85 @@
{ {
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.", "preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
"preview_chart_threshold_label": "Threshold", "preview_chart_threshold_label": "Threshold",
"placeholder_label_key_pair": "Click here to enter a label (key value pairs)", "placeholder_label_key_pair": "Click here to enter a label (key value pairs)",
"button_yes": "Yes", "button_yes": "Yes",
"button_no": "No", "button_no": "No",
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?", "remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
"remove_label_success": "Labels cleared", "remove_label_success": "Labels cleared",
"alert_form_step1": "Step 1 - Define the metric", "alert_form_step1": "Step 1 - Define the metric",
"alert_form_step2": "Step 2 - Define Alert Conditions", "alert_form_step2": "Step 2 - Define Alert Conditions",
"alert_form_step3": "Step 3 - Alert Configuration", "alert_form_step3": "Step 3 - Alert Configuration",
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries", "metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
"confirm_save_title": "Save Changes", "confirm_save_title": "Save Changes",
"confirm_save_content_part1": "Your alert built with", "confirm_save_content_part1": "Your alert built with",
"confirm_save_content_part2": "query will be saved. Press OK to confirm.", "confirm_save_content_part2": "query will be saved. Press OK to confirm.",
"unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin", "unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin",
"rule_created": "Rule created successfully", "rule_created": "Rule created successfully",
"rule_edited": "Rule edited successfully", "rule_edited": "Rule edited successfully",
"expression_missing": "expression is missing in {{where}}", "expression_missing": "expression is missing in {{where}}",
"metricname_missing": "metric name is missing in {{where}}", "metricname_missing": "metric name is missing in {{where}}",
"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",
"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",
"button_cancelchanges": "Cancel", "button_cancelchanges": "Cancel",
"button_discard": "Discard", "button_discard": "Discard",
"text_condition1": "Send a notification when the metric is", "text_condition1": "Send a notification when",
"text_condition2": "the threshold", "text_condition2": "the threshold",
"text_condition3": "during the last", "text_condition3": "during the last",
"option_5min": "5 mins", "option_5min": "5 mins",
"option_10min": "10 mins", "option_10min": "10 mins",
"option_15min": "15 mins", "option_15min": "15 mins",
"option_60min": "60 mins", "option_60min": "60 mins",
"option_4hours": "4 hours", "option_4hours": "4 hours",
"option_24hours": "24 hours", "option_24hours": "24 hours",
"field_threshold": "Alert Threshold", "field_threshold": "Alert Threshold",
"option_allthetimes": "all the times", "option_allthetimes": "all the times",
"option_atleastonce": "at least once", "option_atleastonce": "at least once",
"option_onaverage": "on average", "option_onaverage": "on average",
"option_intotal": "in total", "option_intotal": "in total",
"option_above": "above", "option_above": "above",
"option_below": "below", "option_below": "below",
"option_equal": "is equal to", "option_equal": "is equal to",
"option_notequal": "not equal to", "option_notequal": "not equal to",
"button_query": "Query", "button_query": "Query",
"button_formula": "Formula", "button_formula": "Formula",
"tab_qb": "Query Builder", "tab_qb": "Query Builder",
"tab_promql": "PromQL", "tab_promql": "PromQL",
"title_confirm": "Confirm", "title_confirm": "Confirm",
"button_ok": "Yes", "button_ok": "Yes",
"button_cancel": "No", "button_cancel": "No",
"field_promql_expr": "PromQL Expression", "field_promql_expr": "PromQL Expression",
"field_alert_name": "Alert Name", "field_alert_name": "Alert Name",
"field_alert_desc": "Alert Description", "field_alert_desc": "Alert Description",
"field_labels": "Labels", "field_labels": "Labels",
"field_severity": "Severity", "field_severity": "Severity",
"option_critical": "Critical", "option_critical": "Critical",
"option_error": "Error", "option_error": "Error",
"option_warning": "Warning", "option_warning": "Warning",
"option_info": "Info", "option_info": "Info",
"user_guide_headline": "Steps to create an Alert", "user_guide_headline": "Steps to create an Alert",
"user_guide_qb_step1": "Step 1 - Define the metric", "user_guide_qb_step1": "Step 1 - Define the metric",
"user_guide_qb_step1a": "Choose a metric which you want to create an alert on", "user_guide_qb_step1a": "Choose a metric which you want to create an alert on",
"user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed", "user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed",
"user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric", "user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric",
"user_guide_qb_step1d": "Create a formula based on Queries if needed", "user_guide_qb_step1d": "Create a formula based on Queries if needed",
"user_guide_qb_step2": "Step 2 - Define Alert Conditions", "user_guide_qb_step2": "Step 2 - Define Alert Conditions",
"user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value", "user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value",
"user_guide_qb_step2b": "Enter the Alert threshold", "user_guide_qb_step2b": "Enter the Alert threshold",
"user_guide_qb_step3": "Step 3 -Alert Configuration", "user_guide_qb_step3": "Step 3 -Alert Configuration",
"user_guide_qb_step3a": "Set alert severity, name and descriptions", "user_guide_qb_step3a": "Set alert severity, name and descriptions",
"user_guide_qb_step3b": "Add tags to the alert in the Label field if needed", "user_guide_qb_step3b": "Add tags to the alert in the Label field if needed",
"user_guide_pql_step1": "Step 1 - Define the metric", "user_guide_pql_step1": "Step 1 - Define the metric",
"user_guide_pql_step1a": "Write a PromQL query for the metric", "user_guide_pql_step1a": "Write a PromQL query for the metric",
"user_guide_pql_step1b": "Format the legends based on labels you want to highlight", "user_guide_pql_step1b": "Format the legends based on labels you want to highlight",
"user_guide_pql_step2": "Step 2 - Define Alert Conditions", "user_guide_pql_step2": "Step 2 - Define Alert Conditions",
"user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value", "user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value",
"user_guide_pql_step2b": "Enter the Alert threshold", "user_guide_pql_step2b": "Enter the Alert threshold",
"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_tooltip_more_help": "More details on how to create alerts"
} }

View File

@ -5,12 +5,16 @@ function PromqlSection(): JSX.Element {
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
return ( return (
<PromQLQueryBuilder <>
key="A" {currentQuery.promql.map((query, index) => (
queryIndex={0} <PromQLQueryBuilder
queryData={currentQuery.promql[0]} key={query.name}
deletable={false} queryIndex={index}
/> queryData={query}
deletable={false}
/>
))}
</>
); );
} }

View File

@ -7,6 +7,7 @@ import {
Space, Space,
Typography, Typography,
} from 'antd'; } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
import { import {
getCategoryByOptionId, getCategoryByOptionId,
getCategorySelectOptionByName, getCategorySelectOptionByName,
@ -28,6 +29,7 @@ function RuleOptions({
alertDef, alertDef,
setAlertDef, setAlertDef,
queryCategory, queryCategory,
queryOptions,
}: RuleOptionsProps): JSX.Element { }: RuleOptionsProps): JSX.Element {
// init namespace for translations // init namespace for translations
const { t } = useTranslation('alerts'); const { t } = useTranslation('alerts');
@ -44,6 +46,18 @@ function RuleOptions({
}); });
}; };
const onChangeSelectedQueryName = (value: string | unknown): void => {
if (typeof value !== 'string') return;
setAlertDef({
...alertDef,
condition: {
...alertDef.condition,
selectedQueryName: value,
},
});
};
const renderCompareOps = (): JSX.Element => ( const renderCompareOps = (): JSX.Element => (
<InlineSelect <InlineSelect
getPopupContainer={popupContainer} getPopupContainer={popupContainer}
@ -122,16 +136,38 @@ function RuleOptions({
const renderThresholdRuleOpts = (): JSX.Element => ( const renderThresholdRuleOpts = (): JSX.Element => (
<Form.Item> <Form.Item>
<Typography.Text> <Typography.Text>
{t('text_condition1')} {renderCompareOps()} {t('text_condition2')}{' '} {t('text_condition1')}
{renderThresholdMatchOpts()} {t('text_condition3')} {renderEvalWindows()} <InlineSelect
getPopupContainer={popupContainer}
allowClear
showSearch
options={queryOptions}
placeholder={t('selected_query_placeholder')}
value={alertDef.condition.selectedQueryName}
onChange={onChangeSelectedQueryName}
/>
<Typography.Text>is</Typography.Text>
{renderCompareOps()} {t('text_condition2')} {renderThresholdMatchOpts()}{' '}
{t('text_condition3')} {renderEvalWindows()}
</Typography.Text> </Typography.Text>
</Form.Item> </Form.Item>
); );
const renderPromRuleOptions = (): JSX.Element => ( const renderPromRuleOptions = (): JSX.Element => (
<Form.Item> <Form.Item>
<Typography.Text> <Typography.Text>
{t('text_condition1')} {renderCompareOps()} {t('text_condition2')}{' '} {t('text_condition1')}
{renderPromMatchOpts()} <InlineSelect
getPopupContainer={popupContainer}
allowClear
showSearch
options={queryOptions}
placeholder={t('selected_query_placeholder')}
value={alertDef.condition.selectedQueryName}
onChange={onChangeSelectedQueryName}
/>
<Typography.Text>is</Typography.Text>
{renderCompareOps()} {t('text_condition2')} {renderPromMatchOpts()}
</Typography.Text> </Typography.Text>
</Form.Item> </Form.Item>
); );
@ -172,7 +208,7 @@ function RuleOptions({
? renderPromRuleOptions() ? renderPromRuleOptions()
: renderThresholdRuleOpts()} : renderThresholdRuleOpts()}
<Space align="start"> <Space direction="horizontal" align="center">
<Form.Item noStyle name={['condition', 'target']}> <Form.Item noStyle name={['condition', 'target']}>
<InputNumber <InputNumber
addonBefore={t('field_threshold')} addonBefore={t('field_threshold')}
@ -183,7 +219,7 @@ function RuleOptions({
/> />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item noStyle>
<Select <Select
getPopupContainer={popupContainer} getPopupContainer={popupContainer}
allowClear allowClear
@ -204,5 +240,6 @@ interface RuleOptionsProps {
alertDef: AlertDef; alertDef: AlertDef;
setAlertDef: (a: AlertDef) => void; setAlertDef: (a: AlertDef) => void;
queryCategory: EQueryType; queryCategory: EQueryType;
queryOptions: DefaultOptionType[];
} }
export default RuleOptions; export default RuleOptions;

View File

@ -1,5 +1,12 @@
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons'; import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
import { Col, FormInstance, Modal, Tooltip, Typography } from 'antd'; import {
Col,
FormInstance,
Modal,
SelectProps,
Tooltip,
Typography,
} from 'antd';
import saveAlertApi from 'api/alerts/save'; import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert'; import testAlertApi from 'api/alerts/testAlert';
import { FeatureKeys } from 'constants/features'; import { FeatureKeys } from 'constants/features';
@ -44,6 +51,7 @@ import {
StyledLeftContainer, StyledLeftContainer,
} from './styles'; } from './styles';
import UserGuide from './UserGuide'; import UserGuide from './UserGuide';
import { getSelectedQueryOptions } from './utils';
function FormAlertRules({ function FormAlertRules({
alertType, alertType,
@ -80,6 +88,20 @@ function FormAlertRules({
initialValue, initialValue,
]); ]);
const queryOptions = useMemo(() => {
const queryConfig: Record<EQueryType, () => SelectProps['options']> = {
[EQueryType.QUERY_BUILDER]: () => [
...(getSelectedQueryOptions(currentQuery.builder.queryData) || []),
...(getSelectedQueryOptions(currentQuery.builder.queryFormulas) || []),
],
[EQueryType.PROM]: () => getSelectedQueryOptions(currentQuery.promql),
[EQueryType.CLICKHOUSE]: () =>
getSelectedQueryOptions(currentQuery.clickhouse_sql),
};
return queryConfig[currentQuery.queryType]?.() || [];
}, [currentQuery]);
const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]); const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]);
useShareBuilderUrl(sq); useShareBuilderUrl(sq);
@ -88,6 +110,18 @@ function FormAlertRules({
setAlertDef(initialValue); setAlertDef(initialValue);
}, [initialValue]); }, [initialValue]);
useEffect(() => {
// Set selectedQueryName based on the length of queryOptions
setAlertDef((def) => ({
...def,
condition: {
...def.condition,
selectedQueryName:
queryOptions.length > 0 ? String(queryOptions[0].value) : undefined,
},
}));
}, [currentQuery?.queryType, queryOptions]);
const onCancelHandler = useCallback(() => { const onCancelHandler = useCallback(() => {
history.replace(ROUTES.LIST_ALL_ALERT); history.replace(ROUTES.LIST_ALL_ALERT);
}, []); }, []);
@ -437,6 +471,7 @@ function FormAlertRules({
queryCategory={currentQuery.queryType} queryCategory={currentQuery.queryType}
alertDef={alertDef} alertDef={alertDef}
setAlertDef={setAlertDef} setAlertDef={setAlertDef}
queryOptions={queryOptions}
/> />
{renderBasicInfo()} {renderBasicInfo()}

View File

@ -59,8 +59,8 @@ export const StepHeading = styled.p`
export const InlineSelect = styled(Select)` export const InlineSelect = styled(Select)`
display: inline-block; display: inline-block;
width: 10% !important; width: 10% !important;
margin-left: 0.2em; margin-left: 0.3em;
margin-right: 0.2em; margin-right: 0.3em;
`; `;
export const SeveritySelect = styled(Select)` export const SeveritySelect = styled(Select)`

View File

@ -1,6 +1,13 @@
import { SelectProps } from 'antd';
import { Time } from 'container/TopNav/DateTimeSelection/config'; import { Time } from 'container/TopNav/DateTimeSelection/config';
import getStartEndRangeTime from 'lib/getStartEndRangeTime'; import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import {
IBuilderFormula,
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
} from 'types/api/queryBuilder/queryBuilderData';
// toChartInterval converts eval window to chart selection time interval // toChartInterval converts eval window to chart selection time interval
export const toChartInterval = (evalWindow: string | undefined): Time => { export const toChartInterval = (evalWindow: string | undefined): Time => {
@ -35,3 +42,15 @@ export const getUpdatedStepInterval = (evalWindow?: string): number => {
inputFormat: 'ns', inputFormat: 'ns',
}); });
}; };
export const getSelectedQueryOptions = (
queries: Array<
IBuilderQuery | IBuilderFormula | IClickHouseQuery | IPromQLQuery
>,
): SelectProps['options'] =>
queries
.filter((query) => !query.disabled)
.map((query) => ({
label: 'queryName' in query ? query.queryName : query.name,
value: 'queryName' in query ? query.queryName : query.name,
}));

View File

@ -98,6 +98,11 @@ function GridCardGraph({
const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET; const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET;
const menuList =
widget.panelTypes === PANEL_TYPES.TABLE
? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts)
: headerMenuList;
return ( return (
<span ref={graphRef}> <span ref={graphRef}>
<WidgetGraphComponent <WidgetGraphComponent
@ -109,7 +114,7 @@ function GridCardGraph({
name={name} name={name}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
threshold={threshold} threshold={threshold}
headerMenuList={headerMenuList} headerMenuList={menuList}
onClickHandler={onClickHandler} onClickHandler={onClickHandler}
/> />

View File

@ -1,4 +1,5 @@
import { import {
AlertOutlined,
CopyOutlined, CopyOutlined,
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
@ -157,7 +158,7 @@ function WidgetHeader({
}, },
{ {
key: MenuItemKeys.CreateAlerts, key: MenuItemKeys.CreateAlerts,
icon: <DeleteOutlined />, icon: <AlertOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.CreateAlerts], label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.CreateAlerts],
isVisible: headerMenuList?.includes(MenuItemKeys.CreateAlerts) || false, isVisible: headerMenuList?.includes(MenuItemKeys.CreateAlerts) || false,
disabled: false, disabled: false,

View File

@ -7,6 +7,7 @@ export const EditMenuAction = [
MenuItemKeys.Clone, MenuItemKeys.Clone,
MenuItemKeys.Delete, MenuItemKeys.Delete,
MenuItemKeys.Edit, MenuItemKeys.Edit,
MenuItemKeys.CreateAlerts,
]; ];
export const headerMenuList = [...ViewMenuAction]; export const headerMenuList = [...ViewMenuAction];

View File

@ -16,7 +16,7 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { GraphTitle } from '../constant'; import { GraphTitle, MENU_ITEMS } from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, Row } from '../styles'; import { Card, GraphContainer, Row } from '../styles';
import { Button } from './styles'; import { Button } from './styles';
@ -109,6 +109,7 @@ function DBCall(): JSX.Element {
<Graph <Graph
name="database_call_rps" name="database_call_rps"
widget={databaseCallsRPSWidget} widget={databaseCallsRPSWidget}
headerMenuList={MENU_ITEMS}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)( onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent, ChartEvent,
@ -142,6 +143,7 @@ function DBCall(): JSX.Element {
<Graph <Graph
name="database_call_avg_duration" name="database_call_avg_duration"
widget={databaseCallsAverageDurationWidget} widget={databaseCallsAverageDurationWidget}
headerMenuList={MENU_ITEMS}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)( onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent, ChartEvent,

View File

@ -17,7 +17,7 @@ import { useParams } from 'react-router-dom';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { GraphTitle, legend } from '../constant'; import { GraphTitle, legend, MENU_ITEMS } from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, Row } from '../styles'; import { Card, GraphContainer, Row } from '../styles';
import { Button } from './styles'; import { Button } from './styles';
@ -148,6 +148,7 @@ function External(): JSX.Element {
<Card> <Card>
<GraphContainer> <GraphContainer>
<Graph <Graph
headerMenuList={MENU_ITEMS}
name="external_call_error_percentage" name="external_call_error_percentage"
widget={externalCallErrorWidget} widget={externalCallErrorWidget}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -183,6 +184,7 @@ function External(): JSX.Element {
<GraphContainer> <GraphContainer>
<Graph <Graph
name="external_call_duration" name="external_call_duration"
headerMenuList={MENU_ITEMS}
widget={externalCallDurationWidget} widget={externalCallDurationWidget}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)( onGraphClickHandler(setSelectedTimeStamp)(
@ -219,6 +221,7 @@ function External(): JSX.Element {
<Graph <Graph
name="external_call_rps_by_address" name="external_call_rps_by_address"
widget={externalCallRPSWidget} widget={externalCallRPSWidget}
headerMenuList={MENU_ITEMS}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)( onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent, ChartEvent,
@ -253,6 +256,7 @@ function External(): JSX.Element {
<Graph <Graph
name="external_call_duration_by_address" name="external_call_duration_by_address"
widget={externalCallDurationAddressWidget} widget={externalCallDurationAddressWidget}
headerMenuList={MENU_ITEMS}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onGraphClickHandler(setSelectedTimeStamp)( onGraphClickHandler(setSelectedTimeStamp)(
ChartEvent, ChartEvent,

View File

@ -10,8 +10,8 @@ function ApDexMetricsApplication({
handleGraphClick, handleGraphClick,
onDragSelect, onDragSelect,
tagFilterItems, tagFilterItems,
topLevelOperationsRoute,
thresholdValue, thresholdValue,
topLevelOperationsRoute,
}: ApDexDataSwitcherProps): JSX.Element { }: ApDexDataSwitcherProps): JSX.Element {
const { data, isLoading, error } = useGetMetricMeta(metricMeta); const { data, isLoading, error } = useGetMetricMeta(metricMeta);
useErrorNotification(error); useErrorNotification(error);
@ -22,11 +22,11 @@ function ApDexMetricsApplication({
return ( return (
<ApDexMetrics <ApDexMetrics
topLevelOperationsRoute={topLevelOperationsRoute}
handleGraphClick={handleGraphClick} handleGraphClick={handleGraphClick}
delta={data?.data.delta} delta={data?.data.delta}
metricsBuckets={data?.data.le || []} metricsBuckets={data?.data.le || []}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
topLevelOperationsRoute={topLevelOperationsRoute}
tagFilterItems={tagFilterItems} tagFilterItems={tagFilterItems}
thresholdValue={thresholdValue} thresholdValue={thresholdValue}
/> />

View File

@ -1,4 +1,5 @@
import { DownloadOptions } from 'container/Download/Download.types'; import { DownloadOptions } from 'container/Download/Download.types';
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
export const legend = { export const legend = {
address: '{{address}}', address: '{{address}}',
@ -13,6 +14,8 @@ export const LATENCY_AGGREGATEOPERATOR_SPAN_METRICS = [
]; ];
export const OPERATION_LEGENDS = ['Operations']; export const OPERATION_LEGENDS = ['Operations'];
export const MENU_ITEMS = [MenuItemKeys.View, MenuItemKeys.CreateAlerts];
export enum FORMULA { export enum FORMULA {
ERROR_PERCENTAGE = 'A*100/B', ERROR_PERCENTAGE = 'A*100/B',
DATABASE_CALLS_AVG_DURATION = 'A/B', DATABASE_CALLS_AVG_DURATION = 'A/B',
@ -21,6 +24,8 @@ export enum FORMULA {
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A', APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
} }
export const TOP_LEVEL_OPERATIONS = ['{{.top_level_operations}}'];
export enum GraphTitle { export enum GraphTitle {
APDEX = 'Apdex', APDEX = 'Apdex',
LATENCY = 'Latency', LATENCY = 'Latency',

View File

@ -29,10 +29,8 @@ function YAxisUnitSelector({
value: options.name, value: options.name,
})); }));
return ( return (
<Col style={{ marginTop: '1rem' }}> <Col style={{ marginBottom: 12, marginTop: 12 }}>
<div style={{ margin: '0.5rem 0' }}> <Typography.Text>{fieldLabel}</Typography.Text>
<Typography.Text>{fieldLabel}</Typography.Text>
</div>
<AutoComplete <AutoComplete
style={{ width: '100%' }} style={{ width: '100%' }}
options={options} options={options}

View File

@ -1,9 +1,14 @@
import { Input, Select } from 'antd'; import { UploadOutlined } from '@ant-design/icons';
import { Button, Input, Select, Space } from 'antd';
import InputComponent from 'components/Input'; import InputComponent from 'components/Input';
import TimePreference from 'components/TimePreferenceDropDown'; import TimePreference from 'components/TimePreferenceDropDown';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems'; import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems';
import history from 'lib/history';
import { Dispatch, SetStateAction, useCallback } from 'react'; import { Dispatch, SetStateAction, useCallback } from 'react';
import { Widgets } from 'types/api/dashboard/getAll';
import { Container, Title } from './styles'; import { Container, Title } from './styles';
import { timePreferance } from './timeItems'; import { timePreferance } from './timeItems';
@ -23,6 +28,7 @@ function RightContainer({
yAxisUnit, yAxisUnit,
setYAxisUnit, setYAxisUnit,
setGraphHandler, setGraphHandler,
selectedWidget,
}: RightContainerProps): JSX.Element { }: RightContainerProps): JSX.Element {
const onChangeHandler = useCallback( const onChangeHandler = useCallback(
(setFunc: Dispatch<SetStateAction<string>>, value: string) => { (setFunc: Dispatch<SetStateAction<string>>, value: string) => {
@ -34,6 +40,16 @@ function RightContainer({
const selectedGraphType = const selectedGraphType =
GraphTypes.find((e) => e.name === selectedGraph)?.display || ''; GraphTypes.find((e) => e.name === selectedGraph)?.display || '';
const onCreateAlertsHandler = useCallback(() => {
if (!selectedWidget) return;
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(selectedWidget?.query),
)}`,
);
}, [selectedWidget]);
return ( return (
<Container> <Container>
<Title>Panel Type</Title> <Title>Panel Type</Title>
@ -116,17 +132,26 @@ function RightContainer({
<Title light="true">Panel Time Preference</Title> <Title light="true">Panel Time Preference</Title>
<TimePreference <Space direction="vertical">
{...{ <TimePreference
selectedTime, {...{
setSelectedTime, selectedTime,
}} setSelectedTime,
/> }}
<YAxisUnitSelector />
defaultValue={yAxisUnit}
onSelect={setYAxisUnit} <YAxisUnitSelector
fieldLabel={selectedGraphType === 'Value' ? 'Unit' : 'Y Axis Unit'} defaultValue={yAxisUnit}
/> onSelect={setYAxisUnit}
fieldLabel={selectedGraphType === 'Value' ? 'Unit' : 'Y Axis Unit'}
/>
{selectedWidget?.panelTypes !== PANEL_TYPES.TABLE && (
<Button icon={<UploadOutlined />} onClick={onCreateAlertsHandler}>
Create Alerts from Queries
</Button>
)}
</Space>
</Container> </Container>
); );
} }
@ -148,6 +173,11 @@ interface RightContainerProps {
yAxisUnit: string; yAxisUnit: string;
setYAxisUnit: Dispatch<SetStateAction<string>>; setYAxisUnit: Dispatch<SetStateAction<string>>;
setGraphHandler: (type: PANEL_TYPES) => void; setGraphHandler: (type: PANEL_TYPES) => void;
selectedWidget?: Widgets;
} }
RightContainer.defaultProps = {
selectedWidget: undefined,
};
export default RightContainer; export default RightContainer;

View File

@ -21,6 +21,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { generatePath, useLocation, useParams } from 'react-router-dom'; import { generatePath, useLocation, useParams } from 'react-router-dom';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
@ -100,9 +101,13 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const updateDashboardMutation = useUpdateDashboard(); const updateDashboardMutation = useUpdateDashboard();
const onClickSaveHandler = useCallback(() => { const { afterWidgets, preWidgets } = useMemo(() => {
if (!selectedDashboard) { if (!selectedDashboard) {
return; return {
selectedWidget: {} as Widgets,
preWidgets: [],
afterWidgets: [],
};
} }
const widgetId = query.get('widgetId'); const widgetId = query.get('widgetId');
@ -120,6 +125,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedWidgetIndex || 0 selectedWidgetIndex || 0
]; ];
return { selectedWidget, preWidgets, afterWidgets };
}, [selectedDashboard, query]);
const onClickSaveHandler = useCallback(() => {
if (!selectedDashboard) {
return;
}
updateDashboardMutation.mutateAsync( updateDashboardMutation.mutateAsync(
{ {
uuid: selectedDashboard.uuid, uuid: selectedDashboard.uuid,
@ -128,7 +141,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
widgets: [ widgets: [
...preWidgets, ...preWidgets,
{ {
...selectedWidget, ...(selectedWidget || ({} as Widgets)),
description, description,
timePreferance: selectedTime.enum, timePreferance: selectedTime.enum,
isStacked: stacked, isStacked: stacked,
@ -157,6 +170,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
}, [ }, [
selectedDashboard, selectedDashboard,
updateDashboardMutation, updateDashboardMutation,
preWidgets,
selectedWidget,
description, description,
selectedTime.enum, selectedTime.enum,
stacked, stacked,
@ -165,7 +180,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
title, title,
yAxisUnit, yAxisUnit,
graphType, graphType,
query, afterWidgets,
featureResponse, featureResponse,
dashboardId, dashboardId,
notifications, notifications,
@ -271,6 +286,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setSelectedTime={setSelectedTime} setSelectedTime={setSelectedTime}
selectedTime={selectedTime} selectedTime={selectedTime}
setYAxisUnit={setYAxisUnit} setYAxisUnit={setYAxisUnit}
selectedWidget={selectedWidget}
/> />
</RightContainerWrapper> </RightContainerWrapper>
</PanelContainer> </PanelContainer>

View File

@ -25,10 +25,11 @@ export interface AlertDef {
export interface RuleCondition { export interface RuleCondition {
compositeQuery: ICompositeMetricQuery; compositeQuery: ICompositeMetricQuery;
op?: string | undefined; op?: string;
target?: number | undefined; target?: number;
matchType?: string | undefined; matchType?: string;
targetUnit?: string | undefined; targetUnit?: string;
selectedQueryName?: string;
} }
export interface Labels { export interface Labels {