/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ import dateMath from '@elastic/datemath'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingChart, EuiSpacer, EuiSuperDatePicker, EuiTitle, } from '@elastic/eui'; import { get, orderBy } from 'lodash'; import moment, { DurationInputArg2 } from 'moment'; import React, { useState } from 'react'; import { EntityAnomalySummaries } from '../../../../server/models/interfaces'; import ContentPanel from '../../../components/ContentPanel/ContentPanel'; import { useDelayedLoader } from '../../../hooks/useDelayedLoader'; import { Anomalies, AnomalyData, DateRange, Detector, Monitor, } from '../../../models/interfaces'; import { generateAnomalyAnnotations } from '../../utils/anomalyResultUtils'; import { AlertsButton } from '../components/AlertsButton/AlertsButton'; import { AnomalyDetailsChart } from '../containers/AnomalyDetailsChart'; import { AnomalyHeatmapChart, HeatmapCell, HeatmapDisplayOption, } from '../containers/AnomalyHeatmapChart'; import { getAnomalyGradeWording, getConfidenceWording, getFeatureBreakdownWording, getFeatureDataWording, getHCTitle, } from '../utils/anomalyChartUtils'; import { DATE_PICKER_QUICK_OPTIONS, INITIAL_ANOMALY_SUMMARY, } from '../utils/constants'; import { AnomalyOccurrenceChart } from './AnomalyOccurrenceChart'; import { FeatureBreakDown } from './FeatureBreakDown'; import { convertTimestampToString } from '../../../utils/utils'; export interface AnomaliesChartProps { onDateRangeChange( startDate: number, endDate: number, dateRangeOption?: string ): void; onZoomRangeChange(startDate: number, endDate: number): void; title: string; bucketizedAnomalies: boolean; anomalySummary: any; dateRange: DateRange; isLoading: boolean; showAlerts?: boolean; isNotSample?: boolean; detector: Detector; monitor?: Monitor; children: React.ReactNode | React.ReactNode[]; isHCDetector?: boolean; isHistorical?: boolean; detectorCategoryField?: string[]; onHeatmapCellSelected?(heatmapCell: HeatmapCell): void; onDisplayOptionChanged?(heatmapDisplayOption: HeatmapDisplayOption): void; selectedHeatmapCell?: HeatmapCell; newDetector?: Detector; zoomRange?: DateRange; anomalyAndFeatureResults: Anomalies[] | undefined; heatmapDisplayOption?: HeatmapDisplayOption; entityAnomalySummaries?: EntityAnomalySummaries[]; selectedCategoryFields?: any[]; handleCategoryFieldsChange(selectedOptions: any[]): void; openOutOfRangeCallOut?: boolean; } export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => { const [datePickerRange, setDatePickerRange] = useState({ start: props.isHistorical ? convertTimestampToString(props.dateRange.startDate) : 'now-7d', end: props.isHistorical ? convertTimestampToString(props.dateRange.endDate) : 'now', }); const [showOutOfRangeCallOut, setShowOutOfRangeCallOut] = useState(false); // for each time series of results, get the anomalies, ignoring feature data let anomalyResults = [] as AnomalyData[][]; get(props, 'anomalyAndFeatureResults', []).forEach( (anomalyAndFeatureResult: Anomalies) => { anomalyResults.push(anomalyAndFeatureResult.anomalies); } ); const handleDateRangeChange = (startDate: number, endDate: number) => { props.onDateRangeChange(startDate, endDate); props.onZoomRangeChange(startDate, endDate); if (!props.isHistorical && endDate < get(props, 'detector.enabledTime')) { setShowOutOfRangeCallOut(true); } else { setShowOutOfRangeCallOut(false); } }; const showLoader = useDelayedLoader(props.isLoading); const handleDatePickerDateRangeChange = ( start: string, end: string, refresh?: boolean ) => { if (start && end) { const startTime: moment.Moment | undefined = dateMath.parse(start); if (startTime) { const endTime: moment.Moment | undefined = start === end && start.startsWith('now/') ? moment(startTime) .add(1, start.slice(start.length - 1) as DurationInputArg2) .subtract(1, 'milliseconds') : dateMath.parse(end); if (endTime) { if ( !refresh && !props.bucketizedAnomalies && startTime.valueOf() >= props.dateRange.startDate && endTime.valueOf() <= props.dateRange.endDate ) { props.onZoomRangeChange(startTime.valueOf(), endTime.valueOf()); } else { handleDateRangeChange(startTime.valueOf(), endTime.valueOf()); } } } } }; const handleDatePickerRangeChange = (start: number, end: number) => { setDatePickerRange({ start: moment(start).format(), end: moment(end).format(), }); }; const datePicker = () => ( { setDatePickerRange({ start: start, end: end }); handleDatePickerDateRangeChange(start, end); }} onRefresh={({ start, end, refreshInterval }) => { handleDatePickerDateRangeChange(start, end, true); }} isPaused={true} commonlyUsedRanges={DATE_PICKER_QUICK_OPTIONS} updateButtonProps={{ fill: false, }} /> ); const setUpAlertsButton = () => ( ); const alertsActionsWithDatePicker = () => { return ( {datePicker()} {setUpAlertsButton()} ); }; const hasValidHCProps = () => { return ( props.isHCDetector && props.onHeatmapCellSelected && props.detectorCategoryField && // For Non-Sample HC detector case, aka realtime HC detector(showAlert == true), // we use anomaly summaries data to render heatmap // we must have function onDisplayOptionChanged and entityAnomalySummaries defined // so that heatmap can work as expected. (props.showAlerts !== true || (props.showAlerts && props.onDisplayOptionChanged && props.entityAnomalySummaries)) ); }; return ( {hasValidHCProps() ? (
{props.isLoading ? ( ) : ( [ categoryField.toLowerCase(), ])} selectedCategoryFields={props.selectedCategoryFields} handleCategoryFieldsChange={ props.handleCategoryFieldsChange } resultIndex={get(props.detector, 'resultIndex')} />, props.isNotSample !== true ? [ ,

Sample anomaly occurrences

{props.selectedHeatmapCell ? getHCTitle( props.selectedHeatmapCell .entityList ) : '-'}

) : ( '-' ) } dateRange={props.dateRange} onDateRangeChange={props.onDateRangeChange} onZoomRangeChange={props.onZoomRangeChange} anomalies={anomalyResults} bucketizedAnomalies={false} anomalySummary={props.anomalySummary} isLoading={props.isLoading} anomalyGradeSeriesName={getAnomalyGradeWording( props.isNotSample )} confidenceSeriesName={getConfidenceWording( props.isNotSample )} showAlerts={props.showAlerts} isNotSample={props.isNotSample} detector={props.detector} isHCDetector={props.isHCDetector} isHistorical={props.isHistorical} selectedHeatmapCell={props.selectedHeatmapCell} />, , , ] : null, ] )}
) : ( )}
{props.children}
); });