From b9d8556f11a2331993e6fadf9ca0971458851070 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 9 Oct 2024 14:36:20 +0530 Subject: [PATCH 1/8] feat: anamoly detection - initial ui --- frontend/public/locales/en-GB/alerts.json | 3 + frontend/public/locales/en-GB/rules.json | 1 + frontend/public/locales/en/alerts.json | 13 +- frontend/public/locales/en/rules.json | 1 + frontend/src/constants/alerts.ts | 1 + frontend/src/constants/query.ts | 1 + .../src/constants/queryFunctionOptions.ts | 13 + .../CreateAlertRule/SelectAlertType/config.ts | 5 + .../CreateAlertRule/SelectAlertType/index.tsx | 4 + .../src/container/CreateAlertRule/defaults.ts | 5 + .../src/container/CreateAlertRule/index.tsx | 26 +- frontend/src/container/EditRules/index.tsx | 22 +- .../container/FormAlertRules/BasicInfo.tsx | 2 +- .../FormAlertRules/FormAlertRules.styles.scss | 64 ++++ .../container/FormAlertRules/QuerySection.tsx | 2 +- .../FormAlertRules/RuleOptions.styles.scss | 6 + .../container/FormAlertRules/RuleOptions.tsx | 178 +++++++-- .../src/container/FormAlertRules/index.tsx | 362 ++++++++++++------ .../src/container/FormAlertRules/styles.ts | 6 +- .../components/QueryFunctions/Function.tsx | 3 +- .../newQueryBuilder/convertNewDataToOld.ts | 86 ++++- .../src/lib/uPlotLib/getUplotChartOptions.ts | 34 +- .../lib/uPlotLib/utils/getUplotChartData.ts | 2 +- .../components/Tabs2/Tabs2.styles.scss | 4 +- frontend/src/types/api/alerts/alertTypes.ts | 1 + frontend/src/types/api/alerts/def.ts | 6 + frontend/src/types/api/widgets/getQuery.ts | 9 + frontend/src/types/common/queryBuilder.ts | 1 + 28 files changed, 668 insertions(+), 193 deletions(-) create mode 100644 frontend/src/container/FormAlertRules/RuleOptions.styles.scss diff --git a/frontend/public/locales/en-GB/alerts.json b/frontend/public/locales/en-GB/alerts.json index 5c5c3b851e..8dd0ccbe47 100644 --- a/frontend/public/locales/en-GB/alerts.json +++ b/frontend/public/locales/en-GB/alerts.json @@ -56,6 +56,7 @@ "option_last": "last", "option_above": "above", "option_below": "below", + "option_above_below": "above/below", "option_equal": "is equal to", "option_notequal": "not equal to", "button_query": "Query", @@ -110,6 +111,8 @@ "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 the metric data.", + "anomaly_based_alert": "Anomaly based Alert", + "anomaly_based_alert_desc": "Send a notification when a condition occurs in the metric data.", "log_based_alert": "Log-based Alert", "log_based_alert_desc": "Send a notification when a condition occurs in the logs data.", "traces_based_alert": "Trace-based Alert", diff --git a/frontend/public/locales/en-GB/rules.json b/frontend/public/locales/en-GB/rules.json index 9ac3641c7a..63ae437d7f 100644 --- a/frontend/public/locales/en-GB/rules.json +++ b/frontend/public/locales/en-GB/rules.json @@ -43,6 +43,7 @@ "option_last": "last", "option_above": "above", "option_below": "below", + "option_above_below": "above/below", "option_equal": "is equal to", "option_notequal": "not equal to", "button_query": "Query", diff --git a/frontend/public/locales/en/alerts.json b/frontend/public/locales/en/alerts.json index 3ad8390731..6adeb7382b 100644 --- a/frontend/public/locales/en/alerts.json +++ b/frontend/public/locales/en/alerts.json @@ -13,9 +13,12 @@ "button_no": "No", "remove_label_confirm": "This action will remove all the labels. Do you want to proceed?", "remove_label_success": "Labels cleared", - "alert_form_step1": "Step 1 - Define the metric", - "alert_form_step2": "Step 2 - Define Alert Conditions", - "alert_form_step3": "Step 3 - Alert Configuration", + "alert_form_step1": "Choose a detection method", + "alert_form_step2": "Define the metric", + "alert_form_step3": "Define Alert Conditions", + "alert_form_step4": "Alert Configuration", + "threshold_alert_desc": "An alert is triggered whenever a metric deviates from an expected threshold.", + "anomaly_detection_alert_desc": "An alert is triggered whenever a metric deviates from an expected pattern.", "metric_query_max_limit": "Can not create query. You can create maximum of 5 queries", "confirm_save_title": "Save Changes", "confirm_save_content_part1": "Your alert built with", @@ -35,6 +38,7 @@ "button_cancelchanges": "Cancel", "button_discard": "Discard", "text_condition1": "Send a notification when", + "text_condition1_anomaly": "Send notification when the observed value for", "text_condition2": "the threshold", "text_condition3": "during the last", "option_1min": "1 min", @@ -56,6 +60,7 @@ "option_last": "last", "option_above": "above", "option_below": "below", + "option_above_below": "above/below", "option_equal": "is equal to", "option_notequal": "not equal to", "button_query": "Query", @@ -109,7 +114,9 @@ "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", + "anomaly_based_alert": "Anomaly based Alert", "metric_based_alert_desc": "Send a notification when a condition occurs in the metric data.", + "anomaly_based_alert_desc": "Send a notification when a condition occurs in the metric data.", "log_based_alert": "Log-based Alert", "log_based_alert_desc": "Send a notification when a condition occurs in the logs data.", "traces_based_alert": "Trace-based Alert", diff --git a/frontend/public/locales/en/rules.json b/frontend/public/locales/en/rules.json index 9ac3641c7a..63ae437d7f 100644 --- a/frontend/public/locales/en/rules.json +++ b/frontend/public/locales/en/rules.json @@ -43,6 +43,7 @@ "option_last": "last", "option_above": "above", "option_below": "below", + "option_above_below": "above/below", "option_equal": "is equal to", "option_notequal": "not equal to", "button_query": "Query", diff --git a/frontend/src/constants/alerts.ts b/frontend/src/constants/alerts.ts index 3565ded3d7..425926ea47 100644 --- a/frontend/src/constants/alerts.ts +++ b/frontend/src/constants/alerts.ts @@ -2,6 +2,7 @@ import { AlertTypes } from 'types/api/alerts/alertTypes'; import { DataSource } from 'types/common/queryBuilder'; export const ALERTS_DATA_SOURCE_MAP: Record = { + [AlertTypes.ANOMALY_BASED_ALERT]: DataSource.METRICS, [AlertTypes.METRICS_BASED_ALERT]: DataSource.METRICS, [AlertTypes.LOGS_BASED_ALERT]: DataSource.LOGS, [AlertTypes.TRACES_BASED_ALERT]: DataSource.TRACES, diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index 3ee0a39634..2214e9487c 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -36,4 +36,5 @@ export enum QueryParams { topic = 'topic', partition = 'partition', selectedTimelineQuery = 'selectedTimelineQuery', + ruleType = 'ruleType', } diff --git a/frontend/src/constants/queryFunctionOptions.ts b/frontend/src/constants/queryFunctionOptions.ts index 4a7b3b0413..df0ad0ed0e 100644 --- a/frontend/src/constants/queryFunctionOptions.ts +++ b/frontend/src/constants/queryFunctionOptions.ts @@ -3,6 +3,10 @@ import { QueryFunctionsTypes } from 'types/common/queryBuilder'; import { SelectOption } from 'types/common/select'; export const metricQueryFunctionOptions: SelectOption[] = [ + { + value: QueryFunctionsTypes.ANOMALY, + label: 'Anomaly', + }, { value: QueryFunctionsTypes.CUTOFF_MIN, label: 'Cut Off Min', @@ -67,6 +71,10 @@ export const metricQueryFunctionOptions: SelectOption[] = [ value: QueryFunctionsTypes.TIME_SHIFT, label: 'Time Shift', }, + { + value: QueryFunctionsTypes.TIME_SHIFT, + label: 'Time Shift', + }, ]; export const logsQueryFunctionOptions: SelectOption[] = [ @@ -80,10 +88,15 @@ interface QueryFunctionConfigType { showInput: boolean; inputType?: string; placeholder?: string; + disabled?: boolean; }; } export const queryFunctionsTypesConfig: QueryFunctionConfigType = { + anomaly: { + showInput: false, + disabled: true, + }, cutOffMin: { showInput: true, inputType: 'text', diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts b/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts index c973684e67..b03b7dd767 100644 --- a/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/config.ts @@ -4,6 +4,11 @@ import { AlertTypes } from 'types/api/alerts/alertTypes'; import { OptionType } from './types'; export const getOptionList = (t: TFunction): OptionType[] => [ + { + title: t('anomaly_based_alert'), + selection: AlertTypes.ANOMALY_BASED_ALERT, + description: t('anomaly_based_alert_desc'), + }, { title: t('metric_based_alert'), selection: AlertTypes.METRICS_BASED_ALERT, diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx index 48075649b7..1a6d14f379 100644 --- a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx @@ -17,6 +17,10 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element { function handleRedirection(option: AlertTypes): void { let url = ''; switch (option) { + case AlertTypes.ANOMALY_BASED_ALERT: + url = + 'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples'; + break; case AlertTypes.METRICS_BASED_ALERT: url = 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples'; diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts index f9735e7644..9393c7bc1a 100644 --- a/frontend/src/container/CreateAlertRule/defaults.ts +++ b/frontend/src/container/CreateAlertRule/defaults.ts @@ -7,9 +7,11 @@ import { import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertDef, + defaultAlgorithm, defaultCompareOp, defaultEvalWindow, defaultMatchType, + defaultSeasonality, } from 'types/api/alerts/def'; import { EQueryType } from 'types/common/dashboard'; @@ -46,6 +48,8 @@ export const alertDefaults: AlertDef = { }, op: defaultCompareOp, matchType: defaultMatchType, + algorithm: defaultAlgorithm, + seasonality: defaultSeasonality, }, labels: { severity: 'warning', @@ -145,6 +149,7 @@ export const exceptionAlertDefaults: AlertDef = { }; export const ALERTS_VALUES_MAP: Record = { + [AlertTypes.ANOMALY_BASED_ALERT]: alertDefaults, [AlertTypes.METRICS_BASED_ALERT]: alertDefaults, [AlertTypes.LOGS_BASED_ALERT]: logAlertDefaults, [AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults, diff --git a/frontend/src/container/CreateAlertRule/index.tsx b/frontend/src/container/CreateAlertRule/index.tsx index f7e491cd70..a02d09414a 100644 --- a/frontend/src/container/CreateAlertRule/index.tsx +++ b/frontend/src/container/CreateAlertRule/index.tsx @@ -2,7 +2,7 @@ import { Form, Row } from 'antd'; import logEvent from 'api/common/logEvent'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { QueryParams } from 'constants/query'; -import FormAlertRules from 'container/FormAlertRules'; +import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; import history from 'lib/history'; import { useEffect, useState } from 'react'; @@ -45,6 +45,7 @@ function CreateRules(): JSX.Element { const onSelectType = (typ: AlertTypes): void => { setAlertType(typ); + switch (typ) { case AlertTypes.LOGS_BASED_ALERT: setInitValues(logAlertDefaults); @@ -55,13 +56,34 @@ function CreateRules(): JSX.Element { case AlertTypes.EXCEPTIONS_BASED_ALERT: setInitValues(exceptionAlertDefaults); break; + case AlertTypes.ANOMALY_BASED_ALERT: + setInitValues({ + ...alertDefaults, + version: version || ENTITY_VERSION_V4, + ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT, + }); + break; default: setInitValues({ ...alertDefaults, version: version || ENTITY_VERSION_V4, + ruleType: AlertDetectionTypes.THRESHOLD_ALERT, }); } - queryParams.set(QueryParams.alertType, typ); + + queryParams.set( + QueryParams.alertType, + typ === AlertTypes.ANOMALY_BASED_ALERT + ? AlertTypes.METRICS_BASED_ALERT + : typ, + ); + + if (typ === AlertTypes.ANOMALY_BASED_ALERT) { + queryParams.set( + QueryParams.ruleType, + AlertDetectionTypes.ANOMALY_DETECTION_ALERT, + ); + } const generatedUrl = `${location.pathname}?${queryParams.toString()}`; history.replace(generatedUrl); }; diff --git a/frontend/src/container/EditRules/index.tsx b/frontend/src/container/EditRules/index.tsx index 206c9d2d3e..b6a32615a6 100644 --- a/frontend/src/container/EditRules/index.tsx +++ b/frontend/src/container/EditRules/index.tsx @@ -7,18 +7,16 @@ function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element { const [formInstance] = Form.useForm(); return ( -
- -
+ ); } diff --git a/frontend/src/container/FormAlertRules/BasicInfo.tsx b/frontend/src/container/FormAlertRules/BasicInfo.tsx index 177c9e0fde..6e686d1188 100644 --- a/frontend/src/container/FormAlertRules/BasicInfo.tsx +++ b/frontend/src/container/FormAlertRules/BasicInfo.tsx @@ -96,7 +96,7 @@ function BasicInfo({ return ( <> - {t('alert_form_step3')} + {t('alert_form_step4')} - {t('alert_form_step1')} + {t('alert_form_step2')}
{renderTabs(alertType)}
{renderQuerySection(currentTab)} diff --git a/frontend/src/container/FormAlertRules/RuleOptions.styles.scss b/frontend/src/container/FormAlertRules/RuleOptions.styles.scss new file mode 100644 index 0000000000..5d2a24f0b3 --- /dev/null +++ b/frontend/src/container/FormAlertRules/RuleOptions.styles.scss @@ -0,0 +1,6 @@ +.rule-definition { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} diff --git a/frontend/src/container/FormAlertRules/RuleOptions.tsx b/frontend/src/container/FormAlertRules/RuleOptions.tsx index ed0792d9d5..9ab9a66678 100644 --- a/frontend/src/container/FormAlertRules/RuleOptions.tsx +++ b/frontend/src/container/FormAlertRules/RuleOptions.tsx @@ -1,3 +1,5 @@ +import './RuleOptions.styles.scss'; + import { Checkbox, Collapse, @@ -18,14 +20,17 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useTranslation } from 'react-i18next'; import { AlertDef, + defaultAlgorithm, defaultCompareOp, defaultEvalWindow, defaultFrequency, defaultMatchType, + defaultSeasonality, } from 'types/api/alerts/def'; import { EQueryType } from 'types/common/dashboard'; import { popupContainer } from 'utils/selectPopupContainer'; +import { AlertDetectionTypes } from '.'; import { FormContainer, InlineSelect, @@ -43,6 +48,8 @@ function RuleOptions({ const { t } = useTranslation('alerts'); const { currentQuery } = useQueryBuilder(); + const { ruleType } = alertDef; + const handleMatchOptChange = (value: string | unknown): void => { const m = (value as string) || alertDef.condition?.matchType; setAlertDef({ @@ -86,8 +93,19 @@ function RuleOptions({ > {t('option_above')} {t('option_below')} - {t('option_equal')} - {t('option_notequal')} + + {/* hide equal and not eqaul in case of analmoy based alert */} + + {ruleType !== 'anomaly_rule' && ( + <> + {t('option_equal')} + {t('option_notequal')} + + )} + + {ruleType === 'anomaly_rule' && ( + {t('option_above_below')} + )} ); @@ -101,9 +119,14 @@ function RuleOptions({ > {t('option_atleastonce')} {t('option_allthetimes')} - {t('option_onaverage')} - {t('option_intotal')} - {t('option_last')} + + {ruleType !== 'anomaly_rule' && ( + <> + {t('option_onaverage')} + {t('option_intotal')} + {t('option_last')} + + )} ); @@ -115,6 +138,28 @@ function RuleOptions({ }); }; + const onChangeAlgorithm = (value: string | unknown): void => { + const alg = (value as string) || alertDef.condition.algorithm; + setAlertDef({ + ...alertDef, + condition: { + ...alertDef.condition, + algorithm: alg, + }, + }); + }; + + const onChangeSeasonality = (value: string | unknown): void => { + const seasonality = (value as string) || alertDef.condition.seasonality; + setAlertDef({ + ...alertDef, + condition: { + ...alertDef.condition, + seasonality, + }, + }); + }; + const renderEvalWindows = (): JSX.Element => ( ); + const renderAlgorithms = (): JSX.Element => ( + + Standard + + ); + + const renderSeasonality = (): JSX.Element => ( + + Hourly + Daily + Weekly + + ); + const renderThresholdRuleOpts = (): JSX.Element => ( @@ -166,6 +237,39 @@ function RuleOptions({ ); + const renderAnomalyRuleOpts = ( + onChange: InputNumberProps['onChange'], + ): JSX.Element => ( + + + {t('text_condition1_anomaly')} + + {t('text_condition3')} {renderEvalWindows()} + is + e.currentTarget.blur()} + /> + deviations + {renderCompareOps()} + the predicted data + {renderMatchOpts()} + using the {renderAlgorithms()} algorithm with {renderSeasonality()}{' '} + seasonality + + + ); + const renderPromRuleOptions = (): JSX.Element => ( @@ -245,36 +349,46 @@ function RuleOptions({ return ( <> - {t('alert_form_step2')} + {t('alert_form_step3')} - {queryCategory === EQueryType.PROM - ? renderPromRuleOptions() - : renderThresholdRuleOpts()} + {queryCategory === EQueryType.PROM && renderPromRuleOptions()} + {queryCategory !== EQueryType.PROM && + ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT && ( + <>{renderAnomalyRuleOpts(onChange)} + )} + + {queryCategory !== EQueryType.PROM && + ruleType === AlertDetectionTypes.THRESHOLD_ALERT && + renderThresholdRuleOpts()} - - - e.currentTarget.blur()} - /> - - - - + + + )} + diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 2947b2a0b3..a25b82f7e3 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -3,7 +3,6 @@ import './FormAlertRules.styles.scss'; import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons'; import { Button, - Col, FormInstance, Modal, SelectProps, @@ -13,8 +12,6 @@ import { import saveAlertApi from 'api/alerts/save'; import testAlertApi from 'api/alerts/testAlert'; import logEvent from 'api/common/logEvent'; -import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport'; -import { alertHelpMessage } from 'components/LaunchChatSupport/util'; import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts'; import { FeatureKeys } from 'constants/features'; import { QueryParams } from 'constants/query'; @@ -32,7 +29,9 @@ import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi'; import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi'; -import { isEqual } from 'lodash-es'; +import { cloneDeep, isEqual } from 'lodash-es'; +import { BellDot, ExternalLink } from 'lucide-react'; +import Tabs2 from 'periscope/components/Tabs2'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQueryClient } from 'react-query'; @@ -44,7 +43,11 @@ import { defaultEvalWindow, defaultMatchType, } from 'types/api/alerts/def'; -import { Query } from 'types/api/queryBuilder/queryBuilderData'; +import { + IBuilderQuery, + Query, + QueryFunctionProps, +} from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; import { GlobalReducer } from 'types/reducer/globalTime'; @@ -56,13 +59,16 @@ import { ActionButton, ButtonContainer, MainFormContainer, - PanelContainer, StepContainer, - StyledLeftContainer, + StepHeading, } from './styles'; -import UserGuide from './UserGuide'; import { getSelectedQueryOptions } from './utils'; +export enum AlertDetectionTypes { + THRESHOLD_ALERT = 'threshold_rule', + ANOMALY_DETECTION_ALERT = 'anomaly_rule', +} + // eslint-disable-next-line sonarjs/cognitive-complexity function FormAlertRules({ alertType, @@ -86,6 +92,7 @@ function FormAlertRules({ const { currentQuery, stagedQuery, + handleSetQueryData, handleRunQuery, handleSetConfig, initialDataSource, @@ -108,6 +115,12 @@ function FormAlertRules({ const [alertDef, setAlertDef] = useState(initialValue); const [yAxisUnit, setYAxisUnit] = useState(currentQuery.unit || ''); + const alertTypeFromURL = urlQuery.get(QueryParams.ruleType); + + const [detectionMethod, setDetectionMethod] = useState( + alertTypeFromURL ? 'anomaly_rule' : alertDef.ruleType || 'threshold_rule', + ); + useEffect(() => { if (!isEqual(currentQuery.unit, yAxisUnit)) { setYAxisUnit(currentQuery.unit || ''); @@ -138,6 +151,67 @@ function FormAlertRules({ useShareBuilderUrl(sq); + const findAnomalyQuery = (): { + queryData: IBuilderQuery | undefined; + index: number | undefined; + } => { + const selectedQueryIndex = currentQuery?.builder.queryData.findIndex( + (query) => query.queryName === alertDef?.condition?.selectedQueryName, + ); + + if (selectedQueryIndex !== undefined && selectedQueryIndex >= 0) { + return { + queryData: cloneDeep(currentQuery?.builder.queryData[selectedQueryIndex]), + index: selectedQueryIndex, + }; + } + + return { + queryData: cloneDeep(currentQuery?.builder.queryData[0]), + index: 0, + }; + }; + + const updateFunctions = (data: IBuilderQuery): QueryFunctionProps[] => { + const anomalyFunction = { + name: 'anomaly', + args: [], + namedArgs: { z_score_threshold: 9 }, + }; + const functions = data.functions || []; + + if (alertDef.ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) { + // Add anomaly if not already present + if (!functions.some((func) => func.name === 'anomaly')) { + functions.push(anomalyFunction); + } + } else { + // Remove anomaly if present + const index = functions.findIndex((func) => func.name === 'anomaly'); + if (index !== -1) { + functions.splice(index, 1); + } + } + + return functions; + }; + + const updateFunctionsBasedOnAlertType = (): void => { + const { queryData, index } = findAnomalyQuery(); + + if (queryData && index !== undefined) { + const updatedFunctions = updateFunctions(queryData); + queryData.functions = updatedFunctions; + handleSetQueryData(index, queryData); + } + }; + + useEffect(() => { + console.log('updateFunctionsBasedOnAlertType', alertDef.ruleType); + updateFunctionsBasedOnAlertType(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [detectionMethod, alertDef]); + useEffect(() => { const broadcastToSpecificChannels = (initialValue && @@ -148,8 +222,12 @@ function FormAlertRules({ setAlertDef({ ...initialValue, broadcastToAll: !broadcastToSpecificChannels, + ruleType: + alertTypeFromURL === 'anomaly_rule' + ? 'anomaly_rule' + : initialValue.ruleType, }); - }, [initialValue, isNewRule]); + }, [initialValue, isNewRule, alertTypeFromURL]); useEffect(() => { // Set selectedQueryName based on the length of queryOptions @@ -300,12 +378,15 @@ function FormAlertRules({ const postableAlert: AlertDef = { ...alertDef, preferredChannels: alertDef.broadcastToAll ? [] : alertDef.preferredChannels, - alertType, + alertType: + alertType === AlertTypes.ANOMALY_BASED_ALERT + ? AlertTypes.METRICS_BASED_ALERT + : alertType, source: window?.location.toString(), ruleType: currentQuery.queryType === EQueryType.PROM ? 'promql_rule' - : 'threshold_rule', + : alertDef.ruleType, condition: { ...alertDef.condition, compositeQuery: { @@ -322,6 +403,12 @@ function FormAlertRules({ }, }, }; + + if (alertDef.ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) { + postableAlert.condition.algorithm = alertDef.condition.algorithm; + postableAlert.condition.seasonality = alertDef.condition.seasonality; + } + return postableAlert; }; @@ -585,63 +672,132 @@ function FormAlertRules({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - function handleRedirection(option: AlertTypes): void { - let url = ''; - switch (option) { - case AlertTypes.METRICS_BASED_ALERT: - url = - 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - break; - case AlertTypes.LOGS_BASED_ALERT: - url = - 'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - break; - case AlertTypes.TRACES_BASED_ALERT: - url = - 'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - break; - case AlertTypes.EXCEPTIONS_BASED_ALERT: - url = - 'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - break; - default: - break; - } - logEvent('Alert: Check example alert clicked', { - dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes], - isNewRule: !ruleId || ruleId === 0, - ruleId, - queryType: currentQuery.queryType, - link: url, - }); - window.open(url, '_blank'); - } + // function handleRedirection(option: AlertTypes): void { + // let url = ''; + // switch (option) { + // case AlertTypes.ANOMALY_BASED_ALERT: + // url = + // 'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; + // break; + // case AlertTypes.METRICS_BASED_ALERT: + // url = + // 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; + // break; + // case AlertTypes.LOGS_BASED_ALERT: + // url = + // 'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; + // break; + // case AlertTypes.TRACES_BASED_ALERT: + // url = + // 'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; + // break; + // case AlertTypes.EXCEPTIONS_BASED_ALERT: + // url = + // 'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; + // break; + // default: + // break; + // } + // logEvent('Alert: Check example alert clicked', { + // dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes], + // isNewRule: !ruleId || ruleId === 0, + // ruleId, + // queryType: currentQuery.queryType, + // link: url, + // }); + // window.open(url, '_blank'); + // } + + const tabs = [ + { + value: AlertDetectionTypes.THRESHOLD_ALERT, + label: 'Threshold Alert', + }, + { + value: AlertDetectionTypes.ANOMALY_DETECTION_ALERT, + label: 'Anomaly Detection Alert', + }, + ]; + + const handleDetectionMethodChange = (value: any): void => { + setAlertDef((def) => ({ + ...def, + ruleType: value, + })); + + setDetectionMethod(value); + }; return ( <> {Element} - - - +
+
+
+ {isNewRule && ( + + + + {alertDef.alertType === AlertTypes.ANOMALY_BASED_ALERT && + 'Anomaly Detection Alert'} + {alertDef.alertType === AlertTypes.METRICS_BASED_ALERT && + 'Metrics Based Alert'} + {alertDef.alertType === AlertTypes.LOGS_BASED_ALERT && + 'Logs Based Alert'} + {alertDef.alertType === AlertTypes.TRACES_BASED_ALERT && + 'Traces Based Alert'} + {alertDef.alertType === AlertTypes.EXCEPTIONS_BASED_ALERT && + 'Exceptions Based Alert'} + + )} +
+ + +
+ + +
{currentQuery.queryType === EQueryType.QUERY_BUILDER && renderQBChartPreview()} {currentQuery.queryType === EQueryType.PROM && renderPromAndChQueryChartPreview()} {currentQuery.queryType === EQueryType.CLICKHOUSE && renderPromAndChQueryChartPreview()} +
- - - + + + + +
+ {alertDef.alertType === AlertTypes.METRICS_BASED_ALERT && ( +
+ {t('alert_form_step1')} + + + +
+ {detectionMethod === AlertDetectionTypes.ANOMALY_DETECTION_ALERT + ? t('anomaly_detection_alert_desc') + : t('threshold_alert_desc')} +
+
+ )} {renderBasicInfo()} - - - } - disabled={ - isAlertNameMissing || - isAlertAvailableToSave || - !isChannelConfigurationValid || - queryStatus === 'error' - } - > - {isNewRule ? t('button_createrule') : t('button_savechanges')} - - - +
+ + } disabled={ isAlertNameMissing || + isAlertAvailableToSave || !isChannelConfigurationValid || queryStatus === 'error' } - type="default" - onClick={onTestRuleHandler} - > - {' '} - {t('button_testrule')} - - - {ruleId === 0 && t('button_cancelchanges')} - {ruleId > 0 && t('button_discard')} + {isNewRule ? t('button_createrule') : t('button_savechanges')} - -
- - - -
- - -
- - + {' '} + {t('button_testrule')} + + + {ruleId === 0 && t('button_cancelchanges')} + {ruleId > 0 && t('button_discard')} + + + +
); } diff --git a/frontend/src/container/FormAlertRules/styles.ts b/frontend/src/container/FormAlertRules/styles.ts index 11205c0ab4..d282a484a2 100644 --- a/frontend/src/container/FormAlertRules/styles.ts +++ b/frontend/src/container/FormAlertRules/styles.ts @@ -1,13 +1,9 @@ -import { Button, Card, Col, Form, Input, Row, Select, Typography } from 'antd'; +import { Button, Card, Col, Form, Input, Select, Typography } from 'antd'; import styled from 'styled-components'; const { TextArea } = Input; const { Item } = Form; -export const PanelContainer = styled(Row)` - flex-wrap: nowrap; -`; - export const StyledLeftContainer = styled(Col)` &&& { margin-right: 1rem; diff --git a/frontend/src/container/QueryBuilder/components/QueryFunctions/Function.tsx b/frontend/src/container/QueryBuilder/components/QueryFunctions/Function.tsx index a341d5db55..1611e36444 100644 --- a/frontend/src/container/QueryBuilder/components/QueryFunctions/Function.tsx +++ b/frontend/src/container/QueryBuilder/components/QueryFunctions/Function.tsx @@ -33,7 +33,7 @@ export default function Function({ handleDeleteFunction, }: FunctionProps): JSX.Element { const isDarkMode = useIsDarkMode(); - const { showInput } = queryFunctionsTypesConfig[funcData.name]; + const { showInput, disabled } = queryFunctionsTypesConfig[funcData.name]; let functionValue; @@ -62,6 +62,7 @@ export default function Function({ handleSeriesChange(seriesKey)} + /> + {seriesKey} + + ))} + + + + + )} + + + ); +} + +export default AnomalyAlertEvaluationView; diff --git a/frontend/src/container/AnomalyAlertEvaluationView/index.tsx b/frontend/src/container/AnomalyAlertEvaluationView/index.tsx new file mode 100644 index 0000000000..b99070cf6d --- /dev/null +++ b/frontend/src/container/AnomalyAlertEvaluationView/index.tsx @@ -0,0 +1,3 @@ +import AnomalyAlertEvaluationView from './AnomalyAlertEvaluationView'; + +export default AnomalyAlertEvaluationView; diff --git a/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss b/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss new file mode 100644 index 0000000000..aba753ecc7 --- /dev/null +++ b/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss @@ -0,0 +1,13 @@ +.anomaly-alert-evaluation-view-loading-container { + height: 500px; + display: flex; + justify-content: center; + align-items: center; +} + +.anomaly-alert-evaluation-view-error-container { + height: 500px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index add24106e4..599a12edf9 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -1,8 +1,12 @@ +import './ChartPreview.styles.scss'; + import { InfoCircleOutlined } from '@ant-design/icons'; +import { Card } from 'antd'; import Spinner from 'components/Spinner'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { QueryParams } from 'constants/query'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import AnomalyAlertEvaluationView from 'container/AnomalyAlertEvaluationView'; import GridPanelSwitch from 'container/GridPanelSwitch'; import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; @@ -34,6 +38,7 @@ import { getGraphType } from 'utils/getGraphType'; import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { getTimeRange } from 'utils/getTimeRange'; +import { AlertDetectionTypes } from '..'; import { ChartContainer, FailedMessageContainer } from './styles'; import { getThresholdLabel } from './utils'; @@ -141,6 +146,7 @@ function ChartPreview({ selectedInterval, minTime, maxTime, + alertDef?.ruleType, ], retry: false, enabled: canQuery, @@ -163,8 +169,6 @@ function ChartPreview({ queryResponse.data.payload.data.result = sortedSeriesData; } - const chartData = getUPlotChartData(queryResponse?.data?.payload); - const containerDimensions = useResizeObserver(graphRef); const isDarkMode = useIsDarkMode(); @@ -245,36 +249,75 @@ function ChartPreview({ ], ); + const chartData = getUPlotChartData(queryResponse?.data?.payload); + + const isAnomalyDetectionAlert = + alertDef?.ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT; + + const chartDataAvailable = + chartData && !queryResponse.isError && !queryResponse.isLoading; + return ( - - {headline} - -
- {queryResponse.isLoading && ( - - )} - {(queryResponse?.isError || queryResponse?.error) && ( - - {' '} - {queryResponse.error.message || t('preview_chart_unexpected_error')} - - )} - - {chartData && !queryResponse.isError && !queryResponse.isLoading && ( - - )} -
-
+ <> + {isAnomalyDetectionAlert && ( + <> + {queryResponse.isLoading && ( + + + + )} + {(queryResponse?.isError || queryResponse?.error) && ( + + + {' '} + {queryResponse.error.message || t('preview_chart_unexpected_error')} + + + )} + + {chartDataAvailable && + queryResponse?.data?.payload?.data?.resultType === 'anomaly' && ( + + )} + + )} + + {!isAnomalyDetectionAlert && ( + + {headline} + +
+ {queryResponse.isLoading && ( + + )} + {(queryResponse?.isError || queryResponse?.error) && ( + + + {queryResponse.error.message || t('preview_chart_unexpected_error')} + + )} + + {chartDataAvailable && ( + + )} +
+
+ )} + ); } diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index a25b82f7e3..669c0b5046 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -18,6 +18,7 @@ import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import ROUTES from 'constants/routes'; +import AnomalyAlertEvaluationView from 'container/AnomalyAlertEvaluationView'; import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag'; import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag'; import { BuilderUnitsFilter } from 'container/QueryBuilder/filters'; diff --git a/frontend/src/lib/dashboard/getQueryResults.ts b/frontend/src/lib/dashboard/getQueryResults.ts index 232efad04c..4b9a22d74c 100644 --- a/frontend/src/lib/dashboard/getQueryResults.ts +++ b/frontend/src/lib/dashboard/getQueryResults.ts @@ -32,7 +32,6 @@ export async function GetMetricQueryRange( signal, headers, ); - if (response.statusCode >= 400) { let error = `API responded with ${response.statusCode} - ${response.error} status: ${response.message}`; @@ -71,6 +70,8 @@ export async function GetMetricQueryRange( }, ); } + + console.log('response', response); return response; } diff --git a/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts b/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts index ff0ec112c6..2096068712 100644 --- a/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts +++ b/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts @@ -1,4 +1,5 @@ /* eslint-disable sonarjs/no-identical-functions */ +import cloneDeep from 'lodash-es/cloneDeep'; import { MetricRangePayloadProps, MetricRangePayloadV3, @@ -11,6 +12,12 @@ export const convertNewDataToOld = ( const { result, resultType } = newData.data; const oldResult: MetricRangePayloadProps['data']['result'] = []; + console.log( + 'convertNewDataToOld - newData', + cloneDeep(newData), + cloneDeep(result), + ); + result.forEach((item) => { if (item.series) { item.series.forEach((series) => { @@ -28,7 +35,7 @@ export const convertNewDataToOld = ( const result: QueryData = { metric: series.labels, values, - queryName: `${item.queryName} - Actual`, + queryName: `${item.queryName}`, }; oldResult.push(result); @@ -51,17 +58,15 @@ export const convertNewDataToOld = ( const result: QueryData = { metric: series.labels, values, - queryName: `${item.queryName} - Predicted`, + queryName: `${item.queryName}`, }; oldResult.push(result); }); - - item.series?.push(item.predictedSeries[0]); } - if (item.lowerBoundSeries) { - item.lowerBoundSeries.forEach((series) => { + if (item.upperBoundSeries) { + item.upperBoundSeries.forEach((series) => { const values: QueryData['values'] = series.values.reduce< QueryData['values'] >((acc, currentInfo) => { @@ -76,17 +81,15 @@ export const convertNewDataToOld = ( const result: QueryData = { metric: series.labels, values, - queryName: `${item.queryName} - Lower Bound`, + queryName: `${item.queryName}`, }; oldResult.push(result); }); - - item.series?.push(item.lowerBoundSeries[0]); } - if (item.upperBoundSeries) { - item.upperBoundSeries.forEach((series) => { + if (item.lowerBoundSeries) { + item.lowerBoundSeries.forEach((series) => { const values: QueryData['values'] = series.values.reduce< QueryData['values'] >((acc, currentInfo) => { @@ -101,13 +104,11 @@ export const convertNewDataToOld = ( const result: QueryData = { metric: series.labels, values, - queryName: `${item.queryName} - Upper Bound`, + queryName: `${item.queryName}`, }; oldResult.push(result); }); - - item.series?.push(item.upperBoundSeries[0]); } }); diff --git a/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts b/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts index 4e5339130c..1cbcce289f 100644 --- a/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts +++ b/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts @@ -1,3 +1,4 @@ +import { colors } from 'lib/getRandomColor'; import { cloneDeep, isUndefined } from 'lodash-es'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { QueryData } from 'types/api/widgets/getQuery'; @@ -90,3 +91,45 @@ export const getUPlotChartData = ( : yAxisValuesArr), ]; }; + +const processAnomalyDetectionData = ( + anomalyDetectionData: any, +): Record => { + if (!anomalyDetectionData) { + return {}; + } + + const { + series, + predictedSeries, + upperBoundSeries, + lowerBoundSeries, + } = anomalyDetectionData[0]; + + const processedData: Record = {}; + + console.log('processedData', processedData); + + for (let index = 0; index < series?.length; index++) { + processedData[series[index].labels.service_name] = { + data: [ + series[index].values.map((v: { timestamp: number }) => v.timestamp), + series[index].values.map((v: { value: number }) => v.value), + predictedSeries[index].values.map((v: { value: number }) => v.value), + upperBoundSeries[index].values.map((v: { value: number }) => v.value), + lowerBoundSeries[index].values.map((v: { value: number }) => v.value), + ], + color: colors[index], + }; + } + + return processedData; +}; + +export const getUplotChartDataForAnomalyDetection = ( + apiResponse?: MetricRangePayloadProps, +): Record => { + const anomalyDetectionData = apiResponse?.data?.newResult?.data?.result; + + return processAnomalyDetectionData(anomalyDetectionData); +}; diff --git a/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts b/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts index 9581862c70..fc3f1333e0 100644 --- a/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts +++ b/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts @@ -233,6 +233,38 @@ GetYAxisScale): { auto?: boolean; range?: uPlot.Scale.Range } => { return { auto: false, range: [min, max] }; }; +function getMinMax(data: any): { minValue: number; maxValue: number } { + // Exclude the first array + const arrays = data.slice(1); + + // Flatten the array and convert all elements to float + const flattened = arrays.flat().map(Number); + + // Get min and max, with fallback of 0 for min + const minValue = flattened.length ? Math.min(...flattened) : 0; + const maxValue = Math.max(...flattened); + + return { minValue, maxValue }; +} + +export const getYAxisScaleForAnomalyDetection = ({ + seriesData, + selectedSeries, +}: { + seriesData: any; + selectedSeries: string | null; +}): { auto?: boolean; range?: uPlot.Scale.Range } => { + const selectedSeriesData = seriesData[selectedSeries]; + + if (!selectedSeriesData) { + return { auto: true }; + } + + const { minValue, maxValue } = getMinMax(selectedSeriesData?.data); + + return { auto: false, range: [minValue, maxValue] }; +}; + export type GetYAxisScale = { thresholds?: ThresholdProps[]; series?: QueryDataV3[]; From 902380ef5df15a27ba7aada07b901fbd83f353f2 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Thu, 10 Oct 2024 16:01:59 +0530 Subject: [PATCH 3/8] feat: use antd checkbox --- .../AnomalyAlertEvaluationView.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx index 16738e3255..6782dcbec0 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx @@ -1,6 +1,7 @@ import 'uplot/dist/uPlot.min.css'; import './AnomalyAlertEvaluationView.styles.scss'; +import { Checkbox } from 'antd'; import { useResizeObserver } from 'hooks/useDimensions'; import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData'; import { getXAxisScale } from 'lib/uPlotLib/utils/getXAxisScale'; @@ -270,31 +271,29 @@ function AnomalyAlertEvaluationView({

Select a series to display evaluation view:

{allSeries.map((seriesKey) => ( - + ))} - +
)} From 486798dc12260099c8737d28b9c2b471a268bc0c Mon Sep 17 00:00:00 2001 From: Yunus M Date: Sun, 13 Oct 2024 14:48:09 +0530 Subject: [PATCH 4/8] feat: handle multiple series --- .../AnomalyAlertEvaluationView.styles.scss | 29 +++- .../AnomalyAlertEvaluationView.tsx | 126 +++++++----------- .../src/container/CreateAlertRule/index.tsx | 3 + .../ChartPreview/ChartPreview.styles.scss | 32 +++-- .../FormAlertRules/ChartPreview/index.tsx | 55 ++++---- .../src/container/FormAlertRules/index.tsx | 53 ++++---- frontend/src/lib/dashboard/getQueryResults.ts | 13 +- .../lib/uPlotLib/utils/getUplotChartData.ts | 72 ++++++---- .../src/lib/uPlotLib/utils/getYAxisScale.ts | 13 +- 9 files changed, 223 insertions(+), 173 deletions(-) diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss index d0de862873..43b5047e25 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss @@ -4,29 +4,52 @@ justify-content: space-between; gap: 8px; width: 100%; + height: 100%; background: #121317; - padding: 16px; .anomaly-alert-evaluation-view-chart-section { - height: 500px; + // height: 500px; + height: 100%; width: 100%; + + display: flex; + justify-content: center; + align-items: center; + + .anomaly-alert-evaluation-view-no-data-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + } } .anomaly-alert-evaluation-view-series-selection { display: flex; flex-direction: column; gap: 8px; - width: 240px; + width: 360px; + height: 100%; .anomaly-alert-evaluation-view-series-list { display: flex; flex-direction: column; gap: 8px; + height: 100%; + + .anomaly-alert-evaluation-view-series-list-title { + margin-top: 12px; + font-size: 13px !important; + font-weight: 400; + } .anomaly-alert-evaluation-view-series-list-items { display: flex; flex-direction: column; gap: 8px; + height: 100%; + overflow-y: auto; .anomaly-alert-evaluation-view-series-list-item { display: flex; diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx index 6782dcbec0..fdd6c25907 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx @@ -1,11 +1,14 @@ import 'uplot/dist/uPlot.min.css'; import './AnomalyAlertEvaluationView.styles.scss'; -import { Checkbox } from 'antd'; +import { Checkbox, Typography } from 'antd'; +import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; +import getAxes from 'lib/uPlotLib/utils/getAxes'; import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData'; import { getXAxisScale } from 'lib/uPlotLib/utils/getXAxisScale'; import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale'; +import { LineChart } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import uPlot from 'uplot'; @@ -43,19 +46,22 @@ function AnomalyAlertEvaluationView({ data, minTimeScale, maxTimeScale, + yAxisUnit, }: { data: any; minTimeScale: number | undefined; maxTimeScale: number | undefined; + yAxisUnit: string; }): JSX.Element { const { spline } = uPlot.paths; const _spline = spline ? spline() : undefined; const chartRef = useRef(null); - console.log('AnomalyAlertEvaluationView', data); + // console.log('AnomalyAlertEvaluationView', data); const chartData = getUplotChartDataForAnomalyDetection(data); const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale); + const isDarkMode = useIsDarkMode(); // Example of dynamic seriesData which can have 0 to N series const [seriesData, setSeriesData] = useState(chartData); @@ -81,7 +87,7 @@ function AnomalyAlertEvaluationView({ const bandsPlugin = { hooks: { draw: [ - (u) => { + (u: any): void => { if (!selectedSeries) return; const { ctx } = u; @@ -194,62 +200,18 @@ function AnomalyAlertEvaluationView({ ...getYAxisScaleForAnomalyDetection({ seriesData, selectedSeries, + initialData, + yAxisUnit, }), }, - // y: { - // auto: true, - // range: (u, min, max) => [0, max], - - // range: (u, min, max) => { - // // Get the y-values for the main series, predicted series, upper band, and lower band - // const mainSeries = u.series[1].data || []; - // const predictedSeries = u.series[2]?.data || []; - // const upperBound = u.series[3]?.data || []; - // const lowerBound = u.series[4]?.data || []; - - // console.log('u', u); - - // // Combine all the y-values to find the min and max - // const allYValues = [ - // ...mainSeries, - // ...predictedSeries, - // ...upperBound, - // ...lowerBound, - // ]; - - // const newMin = Math.min(...allYValues); // Find the minimum value - // const newMax = Math.max(...allYValues); // Find the maximum value - - // return [0, max]; // Return the adjusted range - // }, - // }, }, - axes: [ - { - scale: 'x', - stroke: 'white', // Axis line color - grid: { stroke: 'rgba(255,255,255,0.1)' }, // Grid lines color - // ticks: { stroke: 'white' }, // Tick marks color - // font: '12px Arial', // Font for labels - // label: 'X-Axis Label', - size: 50, // Adjust size as needed - }, - { - scale: 'y', - stroke: 'white', // Axis line color - grid: { stroke: 'rgba(255,255,255,0.1)' }, // Grid lines color - // ticks: { stroke: 'white' }, // Tick marks color - // font: '12px Arial', // Font for labels - // label: 'Y-Axis Label', - size: 50, // Adjust size as needed - }, - ], grid: { show: true, }, legend: { show: true, }, + axes: getAxes(isDarkMode, yAxisUnit), }; return ( @@ -262,42 +224,54 @@ function AnomalyAlertEvaluationView({ chartRef={chartRef} /> ) : ( -

No data available

+
+ + + No Data +
)} -
- {allSeries.length > 1 && ( -
-

Select a series to display evaluation view:

-
- {allSeries.map((seriesKey) => ( + + {allSeries.length > 1 && ( +
+ {allSeries.length > 1 && ( +
+ + Select a series + +
handleSeriesChange(seriesKey)} + value="all" + checked={selectedSeries === null} + onChange={(): void => handleSeriesChange(null)} > - {seriesKey} + Show All - ))} - handleSeriesChange(null)} - > - Show All - + {allSeries.map((seriesKey) => ( + handleSeriesChange(seriesKey)} + > + {seriesKey} + + ))} +
-
- )} -
+ )} +
+ )}
); } diff --git a/frontend/src/container/CreateAlertRule/index.tsx b/frontend/src/container/CreateAlertRule/index.tsx index a02d09414a..55c4fdc090 100644 --- a/frontend/src/container/CreateAlertRule/index.tsx +++ b/frontend/src/container/CreateAlertRule/index.tsx @@ -83,7 +83,10 @@ function CreateRules(): JSX.Element { QueryParams.ruleType, AlertDetectionTypes.ANOMALY_DETECTION_ALERT, ); + } else { + queryParams.set(QueryParams.ruleType, AlertDetectionTypes.THRESHOLD_ALERT); } + const generatedUrl = `${location.pathname}?${queryParams.toString()}`; history.replace(generatedUrl); }; diff --git a/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss b/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss index aba753ecc7..f39f050efe 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss +++ b/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss @@ -1,13 +1,23 @@ -.anomaly-alert-evaluation-view-loading-container { - height: 500px; - display: flex; - justify-content: center; - align-items: center; -} +.anomaly-alert-evaluation-view-container { + height: 57vh; + width: 100%; + + .ant-card-body { + height: 100%; + padding: 12px; + } + + .anomaly-alert-evaluation-view-loading-container { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } -.anomaly-alert-evaluation-view-error-container { - height: 500px; - display: flex; - justify-content: center; - align-items: center; + .anomaly-alert-evaluation-view-error-container { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } } diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index 599a12edf9..d7d36b08cf 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -259,33 +259,6 @@ function ChartPreview({ return ( <> - {isAnomalyDetectionAlert && ( - <> - {queryResponse.isLoading && ( - - - - )} - {(queryResponse?.isError || queryResponse?.error) && ( - - - {' '} - {queryResponse.error.message || t('preview_chart_unexpected_error')} - - - )} - - {chartDataAvailable && - queryResponse?.data?.payload?.data?.resultType === 'anomaly' && ( - - )} - - )} - {!isAnomalyDetectionAlert && ( {headline} @@ -317,6 +290,34 @@ function ChartPreview({ )} + + {isAnomalyDetectionAlert && ( + + {queryResponse.isLoading && ( +
+ +
+ )} + {(queryResponse?.isError || queryResponse?.error) && ( +
+ + {' '} + {queryResponse.error.message || t('preview_chart_unexpected_error')} + +
+ )} + + {chartDataAvailable && + queryResponse?.data?.payload?.data?.resultType === 'anomaly' && ( + + )} +
+ )} ); } diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 669c0b5046..9ffcdcbb85 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -18,7 +18,6 @@ import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import ROUTES from 'constants/routes'; -import AnomalyAlertEvaluationView from 'container/AnomalyAlertEvaluationView'; import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag'; import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag'; import { BuilderUnitsFilter } from 'container/QueryBuilder/filters'; @@ -30,13 +29,14 @@ import useUrlQuery from 'hooks/useUrlQuery'; import history from 'lib/history'; import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi'; import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi'; -import { cloneDeep, isEqual } from 'lodash-es'; +import { isEqual } from 'lodash-es'; import { BellDot, ExternalLink } from 'lucide-react'; import Tabs2 from 'periscope/components/Tabs2'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQueryClient } from 'react-query'; import { useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { AlertTypes } from 'types/api/alerts/alertTypes'; import { @@ -87,6 +87,9 @@ function FormAlertRules({ const urlQuery = useUrlQuery(); + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + // In case of alert the panel types should always be "Graph" only const panelType = PANEL_TYPES.TIME_SERIES; @@ -118,8 +121,12 @@ function FormAlertRules({ const alertTypeFromURL = urlQuery.get(QueryParams.ruleType); + console.log('alertTypeFromURL', alertTypeFromURL); + const [detectionMethod, setDetectionMethod] = useState( - alertTypeFromURL ? 'anomaly_rule' : alertDef.ruleType || 'threshold_rule', + alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT + ? AlertDetectionTypes.ANOMALY_DETECTION_ALERT + : AlertDetectionTypes.THRESHOLD_ALERT, ); useEffect(() => { @@ -152,27 +159,6 @@ function FormAlertRules({ useShareBuilderUrl(sq); - const findAnomalyQuery = (): { - queryData: IBuilderQuery | undefined; - index: number | undefined; - } => { - const selectedQueryIndex = currentQuery?.builder.queryData.findIndex( - (query) => query.queryName === alertDef?.condition?.selectedQueryName, - ); - - if (selectedQueryIndex !== undefined && selectedQueryIndex >= 0) { - return { - queryData: cloneDeep(currentQuery?.builder.queryData[selectedQueryIndex]), - index: selectedQueryIndex, - }; - } - - return { - queryData: cloneDeep(currentQuery?.builder.queryData[0]), - index: 0, - }; - }; - const updateFunctions = (data: IBuilderQuery): QueryFunctionProps[] => { const anomalyFunction = { name: 'anomaly', @@ -198,9 +184,9 @@ function FormAlertRules({ }; const updateFunctionsBasedOnAlertType = (): void => { - const { queryData, index } = findAnomalyQuery(); + for (let index = 0; index < currentQuery.builder.queryData.length; index++) { + const queryData = currentQuery.builder.queryData[index]; - if (queryData && index !== undefined) { const updatedFunctions = updateFunctions(queryData); queryData.functions = updatedFunctions; handleSetQueryData(index, queryData); @@ -208,10 +194,9 @@ function FormAlertRules({ }; useEffect(() => { - console.log('updateFunctionsBasedOnAlertType', alertDef.ruleType); updateFunctionsBasedOnAlertType(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [detectionMethod, alertDef]); + }, [detectionMethod, alertDef, currentQuery.builder.queryData.length]); useEffect(() => { const broadcastToSpecificChannels = @@ -726,6 +711,18 @@ function FormAlertRules({ ruleType: value, })); + if (value === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) { + queryParams.set( + QueryParams.ruleType, + AlertDetectionTypes.ANOMALY_DETECTION_ALERT, + ); + } else { + queryParams.set(QueryParams.ruleType, AlertDetectionTypes.THRESHOLD_ALERT); + } + + const generatedUrl = `${location.pathname}?${queryParams.toString()}`; + history.replace(generatedUrl); + setDetectionMethod(value); }; diff --git a/frontend/src/lib/dashboard/getQueryResults.ts b/frontend/src/lib/dashboard/getQueryResults.ts index 4b9a22d74c..6edd44535b 100644 --- a/frontend/src/lib/dashboard/getQueryResults.ts +++ b/frontend/src/lib/dashboard/getQueryResults.ts @@ -71,7 +71,18 @@ export async function GetMetricQueryRange( ); } - console.log('response', response); + if (response.payload?.data?.newResult?.data?.resultType === 'anomaly') { + response.payload.data.newResult.data.result = response.payload.data.newResult.data.result.map( + (queryData) => { + if (legendMap[queryData.queryName]) { + queryData.legend = legendMap[queryData.queryName]; + } + + return queryData; + }, + ); + } + return response; } diff --git a/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts b/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts index 1cbcce289f..d99a4cfd0f 100644 --- a/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts +++ b/frontend/src/lib/uPlotLib/utils/getUplotChartData.ts @@ -1,3 +1,4 @@ +import getLabelName from 'lib/getLabelName'; import { colors } from 'lib/getRandomColor'; import { cloneDeep, isUndefined } from 'lodash-es'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; @@ -99,28 +100,46 @@ const processAnomalyDetectionData = ( return {}; } - const { - series, - predictedSeries, - upperBoundSeries, - lowerBoundSeries, - } = anomalyDetectionData[0]; - - const processedData: Record = {}; - - console.log('processedData', processedData); - - for (let index = 0; index < series?.length; index++) { - processedData[series[index].labels.service_name] = { - data: [ - series[index].values.map((v: { timestamp: number }) => v.timestamp), - series[index].values.map((v: { value: number }) => v.value), - predictedSeries[index].values.map((v: { value: number }) => v.value), - upperBoundSeries[index].values.map((v: { value: number }) => v.value), - lowerBoundSeries[index].values.map((v: { value: number }) => v.value), - ], - color: colors[index], - }; + const processedData: Record< + string, + { data: number[][]; color: string; legendLabel: string } + > = {}; + + for ( + let queryIndex = 0; + queryIndex < anomalyDetectionData.length; + queryIndex++ + ) { + const { + series, + predictedSeries, + upperBoundSeries, + lowerBoundSeries, + queryName, + legend, + } = anomalyDetectionData[queryIndex]; + + for (let index = 0; index < series?.length; index++) { + const label = getLabelName( + series[index].labels, + queryName || '', // query + legend || '', + ); + + const objKey = `${queryName}-${label}`; + + processedData[objKey] = { + data: [ + series[index].values.map((v: { timestamp: number }) => v.timestamp / 1000), + series[index].values.map((v: { value: number }) => v.value), + predictedSeries[index].values.map((v: { value: number }) => v.value), + upperBoundSeries[index].values.map((v: { value: number }) => v.value), + lowerBoundSeries[index].values.map((v: { value: number }) => v.value), + ], + color: colors[index], + legendLabel: label, + }; + } } return processedData; @@ -128,7 +147,14 @@ const processAnomalyDetectionData = ( export const getUplotChartDataForAnomalyDetection = ( apiResponse?: MetricRangePayloadProps, -): Record => { +): Record< + string, + { + [x: string]: any; + data: number[][]; + color: string; + } +> => { const anomalyDetectionData = apiResponse?.data?.newResult?.data?.result; return processAnomalyDetectionData(anomalyDetectionData); diff --git a/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts b/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts index fc3f1333e0..7ac4a3ba2f 100644 --- a/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts +++ b/frontend/src/lib/uPlotLib/utils/getYAxisScale.ts @@ -250,17 +250,22 @@ function getMinMax(data: any): { minValue: number; maxValue: number } { export const getYAxisScaleForAnomalyDetection = ({ seriesData, selectedSeries, + initialData, }: { seriesData: any; selectedSeries: string | null; + initialData: any; + yAxisUnit?: string; }): { auto?: boolean; range?: uPlot.Scale.Range } => { - const selectedSeriesData = seriesData[selectedSeries]; - - if (!selectedSeriesData) { + if (!selectedSeries && !initialData) { return { auto: true }; } - const { minValue, maxValue } = getMinMax(selectedSeriesData?.data); + const selectedSeriesData = selectedSeries + ? seriesData[selectedSeries]?.data + : initialData; + + const { minValue, maxValue } = getMinMax(selectedSeriesData); return { auto: false, range: [minValue, maxValue] }; }; From b5335a161d2726732187d45f3cd27ab030b6109c Mon Sep 17 00:00:00 2001 From: Yunus M Date: Sun, 13 Oct 2024 16:01:58 +0530 Subject: [PATCH 5/8] feat: handle chart height on switch btwn threshold / anomaly --- .../AnomalyAlertEvaluationView.tsx | 34 ++++++++----------- .../ChartPreview/ChartPreview.styles.scss | 7 ++-- .../FormAlertRules/ChartPreview/index.tsx | 29 +++++++++++----- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx index fdd6c25907..3d1c07899c 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx @@ -6,7 +6,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; import getAxes from 'lib/uPlotLib/utils/getAxes'; import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData'; -import { getXAxisScale } from 'lib/uPlotLib/utils/getXAxisScale'; import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale'; import { LineChart } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; @@ -25,15 +24,22 @@ function UplotChart({ useEffect(() => { if (plotInstance.current) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore plotInstance.current.destroy(); } if (data && data.length > 0) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line new-cap plotInstance.current = new uPlot(options, data, chartRef.current); } return (): void => { if (plotInstance.current) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore plotInstance.current.destroy(); } }; @@ -44,33 +50,27 @@ function UplotChart({ function AnomalyAlertEvaluationView({ data, - minTimeScale, - maxTimeScale, yAxisUnit, }: { data: any; - minTimeScale: number | undefined; - maxTimeScale: number | undefined; yAxisUnit: string; }): JSX.Element { const { spline } = uPlot.paths; + // eslint-disable-next-line @typescript-eslint/naming-convention const _spline = spline ? spline() : undefined; const chartRef = useRef(null); - - // console.log('AnomalyAlertEvaluationView', data); - - const chartData = getUplotChartDataForAnomalyDetection(data); - const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale); const isDarkMode = useIsDarkMode(); - - // Example of dynamic seriesData which can have 0 to N series - const [seriesData, setSeriesData] = useState(chartData); - + const [seriesData, setSeriesData] = useState({}); const [selectedSeries, setSelectedSeries] = useState(null); const graphRef = useRef(null); const dimensions = useResizeObserver(graphRef); + useEffect(() => { + const chartData = getUplotChartDataForAnomalyDetection(data); + setSeriesData(chartData); + }, [data]); + useEffect(() => { const seriesKeys = Object.keys(seriesData); if (seriesKeys.length === 1) { @@ -140,11 +140,8 @@ function AnomalyAlertEvaluationView({ : []; const options = { - title: selectedSeries - ? `Evaluation View - (${selectedSeries})` - : 'Evaluation View - All', width: dimensions.width, - height: dimensions.height - 86, + height: dimensions.height - 36, plugins: [bandsPlugin], focus: { alpha: 0.3, @@ -194,7 +191,6 @@ function AnomalyAlertEvaluationView({ scales: { x: { time: true, - // ...timeScaleProps, }, y: { ...getYAxisScaleForAnomalyDetection({ diff --git a/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss b/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss index f39f050efe..f8bcbc693c 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss +++ b/frontend/src/container/FormAlertRules/ChartPreview/ChartPreview.styles.scss @@ -1,9 +1,12 @@ -.anomaly-alert-evaluation-view-container { +.alert-chart-container { height: 57vh; width: 100%; + .threshold-alert-uplot-chart-container { + height: calc(100% - 24px); + } + .ant-card-body { - height: 100%; padding: 12px; } diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index d7d36b08cf..dc9341c509 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -1,7 +1,6 @@ import './ChartPreview.styles.scss'; import { InfoCircleOutlined } from '@ant-design/icons'; -import { Card } from 'antd'; import Spinner from 'components/Spinner'; import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { QueryParams } from 'constants/query'; @@ -200,13 +199,18 @@ function ChartPreview({ [dispatch, location.pathname, urlQuery], ); + console.log('containerDimensions', containerDimensions); + const options = useMemo( () => getUPlotChartOptions({ id: 'alert_legend_widget', yAxisUnit, apiResponse: queryResponse?.data?.payload, - dimensions: containerDimensions, + dimensions: { + height: containerDimensions?.height ? containerDimensions.height - 48 : 0, + width: containerDimensions?.width, + }, minTimeScale, maxTimeScale, isDarkMode, @@ -259,11 +263,11 @@ function ChartPreview({ return ( <> - {!isAnomalyDetectionAlert && ( +
{headline} -
+
{queryResponse.isLoading && ( )} @@ -274,7 +278,7 @@ function ChartPreview({ )} - {chartDataAvailable && ( + {chartDataAvailable && !isAnomalyDetectionAlert && ( )} + + {chartDataAvailable && + isAnomalyDetectionAlert && + queryResponse?.data?.payload?.data?.resultType === 'anomaly' && ( + + )}
- )} +
- {isAnomalyDetectionAlert && ( + {/* {isAnomalyDetectionAlert && ( {queryResponse.isLoading && (
@@ -317,7 +330,7 @@ function ChartPreview({ /> )} - )} + )} */} ); } From 7f44421bac7cfb2ae765674c9eb123d928befc07 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Sun, 13 Oct 2024 17:08:39 +0530 Subject: [PATCH 6/8] feat: do not update url on detection type change --- .../AnomalyAlertEvaluationView.styles.scss | 8 +++- .../AnomalyAlertEvaluationView.tsx | 7 +++- .../FormAlertRules/ChartPreview/index.tsx | 2 - .../src/container/FormAlertRules/index.tsx | 38 +++++++------------ .../newQueryBuilder/convertNewDataToOld.ts | 7 ---- 5 files changed, 25 insertions(+), 37 deletions(-) diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss index 43b5047e25..99b4dbc3b5 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss @@ -8,7 +8,6 @@ background: #121317; .anomaly-alert-evaluation-view-chart-section { - // height: 500px; height: 100%; width: 100%; @@ -16,6 +15,10 @@ justify-content: center; align-items: center; + &.has-multi-series-data { + width: calc(100% - 240px); + } + .anomaly-alert-evaluation-view-no-data-container { display: flex; flex-direction: column; @@ -29,7 +32,8 @@ display: flex; flex-direction: column; gap: 8px; - width: 360px; + width: 240px; + padding: 8px; height: 100%; .anomaly-alert-evaluation-view-series-list { diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx index 3d1c07899c..8357d9bd57 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.tsx @@ -212,7 +212,12 @@ function AnomalyAlertEvaluationView({ return (
-
+
1 ? 'has-multi-series-data' : '' + }`} + ref={graphRef} + > {allSeries.length > 0 ? ( getUPlotChartOptions({ diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 9ffcdcbb85..bab4d75413 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -36,7 +36,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQueryClient } from 'react-query'; import { useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { AlertTypes } from 'types/api/alerts/alertTypes'; import { @@ -87,9 +86,6 @@ function FormAlertRules({ const urlQuery = useUrlQuery(); - const location = useLocation(); - const queryParams = new URLSearchParams(location.search); - // In case of alert the panel types should always be "Graph" only const panelType = PANEL_TYPES.TIME_SERIES; @@ -121,12 +117,8 @@ function FormAlertRules({ const alertTypeFromURL = urlQuery.get(QueryParams.ruleType); - console.log('alertTypeFromURL', alertTypeFromURL); - const [detectionMethod, setDetectionMethod] = useState( - alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT - ? AlertDetectionTypes.ANOMALY_DETECTION_ALERT - : AlertDetectionTypes.THRESHOLD_ALERT, + AlertDetectionTypes.THRESHOLD_ALERT, ); useEffect(() => { @@ -205,14 +197,21 @@ function FormAlertRules({ initialValue.preferredChannels.length > 0) || isNewRule; + let ruleType = AlertDetectionTypes.THRESHOLD_ALERT; + + if (initialValue.ruleType) { + ruleType = initialValue.ruleType as AlertDetectionTypes; + } else if (alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) { + ruleType = AlertDetectionTypes.ANOMALY_DETECTION_ALERT; + } + setAlertDef({ ...initialValue, broadcastToAll: !broadcastToSpecificChannels, - ruleType: - alertTypeFromURL === 'anomaly_rule' - ? 'anomaly_rule' - : initialValue.ruleType, + ruleType, }); + + setDetectionMethod(ruleType); }, [initialValue, isNewRule, alertTypeFromURL]); useEffect(() => { @@ -711,18 +710,6 @@ function FormAlertRules({ ruleType: value, })); - if (value === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) { - queryParams.set( - QueryParams.ruleType, - AlertDetectionTypes.ANOMALY_DETECTION_ALERT, - ); - } else { - queryParams.set(QueryParams.ruleType, AlertDetectionTypes.THRESHOLD_ALERT); - } - - const generatedUrl = `${location.pathname}?${queryParams.toString()}`; - history.replace(generatedUrl); - setDetectionMethod(value); }; @@ -784,6 +771,7 @@ function FormAlertRules({ {t('alert_form_step1')} { if (item.series) { item.series.forEach((series) => { From 57e21c8a49a3cf53c1465d774690ec378630acb9 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Sun, 13 Oct 2024 17:26:11 +0530 Subject: [PATCH 7/8] chore: pr clean up --- .../AnomalyAlertEvaluationView.styles.scss | 2 +- .../FormAlertRules/ChartPreview/index.tsx | 84 ++++++------------- .../src/container/FormAlertRules/index.tsx | 36 -------- .../src/lib/uPlotLib/getUplotChartOptions.ts | 18 +--- 4 files changed, 29 insertions(+), 111 deletions(-) diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss index 99b4dbc3b5..5d6da0c180 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss @@ -5,7 +5,7 @@ gap: 8px; width: 100%; height: 100%; - background: #121317; + background: var(--bg-ink-500); .anomaly-alert-evaluation-view-chart-section { height: 100%; diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index 4d62ed428e..a397801c9a 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -260,76 +260,46 @@ function ChartPreview({ chartData && !queryResponse.isError && !queryResponse.isLoading; return ( - <> -
- - {headline} - -
- {queryResponse.isLoading && ( - - )} - {(queryResponse?.isError || queryResponse?.error) && ( - - - {queryResponse.error.message || t('preview_chart_unexpected_error')} - - )} - - {chartDataAvailable && !isAnomalyDetectionAlert && ( - - )} +
+ + {headline} - {chartDataAvailable && - isAnomalyDetectionAlert && - queryResponse?.data?.payload?.data?.resultType === 'anomaly' && ( - - )} -
- -
- - {/* {isAnomalyDetectionAlert && ( - +
{queryResponse.isLoading && ( -
- -
+ )} {(queryResponse?.isError || queryResponse?.error) && ( -
- - {' '} - {queryResponse.error.message || t('preview_chart_unexpected_error')} - -
+ + + {queryResponse.error.message || t('preview_chart_unexpected_error')} + + )} + + {chartDataAvailable && !isAnomalyDetectionAlert && ( + )} {chartDataAvailable && + isAnomalyDetectionAlert && queryResponse?.data?.payload?.data?.resultType === 'anomaly' && ( )} - - )} */} - +
+
+
); } diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index bab4d75413..1515f52a0a 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -657,42 +657,6 @@ function FormAlertRules({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // function handleRedirection(option: AlertTypes): void { - // let url = ''; - // switch (option) { - // case AlertTypes.ANOMALY_BASED_ALERT: - // url = - // 'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - // break; - // case AlertTypes.METRICS_BASED_ALERT: - // url = - // 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - // break; - // case AlertTypes.LOGS_BASED_ALERT: - // url = - // 'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - // break; - // case AlertTypes.TRACES_BASED_ALERT: - // url = - // 'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - // break; - // case AlertTypes.EXCEPTIONS_BASED_ALERT: - // url = - // 'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples'; - // break; - // default: - // break; - // } - // logEvent('Alert: Check example alert clicked', { - // dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes], - // isNewRule: !ruleId || ruleId === 0, - // ruleId, - // queryType: currentQuery.queryType, - // link: url, - // }); - // window.open(url, '_blank'); - // } - const tabs = [ { value: AlertDetectionTypes.THRESHOLD_ALERT, diff --git a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts index 235d1c28b1..78617669a2 100644 --- a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts +++ b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts @@ -167,23 +167,7 @@ export const getUPlotChartOptions = ({ const series = getStackedSeries(apiResponse?.data?.result || []); - const anomalySeriesBand = [ - { - series: [1, 3], - fill: 'rgba(78, 116, 248, 0.1)', - }, - { - series: [4, 1], - fill: 'rgba(78, 116, 248, 0.1)', - }, - ]; - - // eslint-disable-next-line no-nested-ternary - const bands = isAnomalyRule - ? anomalySeriesBand - : stackBarChart - ? getBands(series) - : null; + const bands = stackBarChart ? getBands(series) : null; return { id, From 9a9a2e71beb0983635ef5f9cb1f016d8f7372bd3 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Sun, 13 Oct 2024 17:31:58 +0530 Subject: [PATCH 8/8] feat: remove chart container background --- .../AnomalyAlertEvaluationView.styles.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss index 5d6da0c180..308dfb68c9 100644 --- a/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss +++ b/frontend/src/container/AnomalyAlertEvaluationView/AnomalyAlertEvaluationView.styles.scss @@ -5,7 +5,6 @@ gap: 8px; width: 100%; height: 100%; - background: var(--bg-ink-500); .anomaly-alert-evaluation-view-chart-section { height: 100%;