44 * you may not use this file except in compliance with the Elastic License.
55 */
66
7- import React , { useMemo , useCallback } from 'react' ;
7+ import React , { useMemo , useCallback , useEffect } from 'react' ;
88import { i18n } from '@kbn/i18n' ;
99import { FormattedMessage } from '@kbn/i18n/react' ;
1010import moment from 'moment' ;
@@ -18,7 +18,12 @@ import {
1818 TooltipValue ,
1919 niceTimeFormatter ,
2020 ElementClickListener ,
21+ RectAnnotation ,
22+ RectAnnotationDatum ,
2123} from '@elastic/charts' ;
24+ import { EuiFlexItem } from '@elastic/eui' ;
25+ import { EuiFlexGroup } from '@elastic/eui' ;
26+ import { EuiIcon } from '@elastic/eui' ;
2227import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public' ;
2328import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n' ;
2429import { MetricsExplorerAggregation } from '../../../../../../common/http_api' ;
@@ -35,6 +40,8 @@ import { calculateDomain } from '../../../metrics_explorer/components/helpers/ca
3540
3641import { euiStyled } from '../../../../../../../observability/public' ;
3742import { InfraFormatter } from '../../../../../lib/lib' ;
43+ import { useMetricsHostsAnomaliesResults } from '../../hooks/use_metrics_hosts_anomalies' ;
44+ import { useMetricsK8sAnomaliesResults } from '../../hooks/use_metrics_k8s_anomalies' ;
3845
3946interface Props {
4047 interval : string ;
@@ -47,7 +54,8 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
4754 const { metric, nodeType, accountId, region } = useWaffleOptionsContext ( ) ;
4855 const { currentTime, jumpToTime, stopAutoReload } = useWaffleTimeContext ( ) ;
4956 const { filterQueryAsJson } = useWaffleFiltersContext ( ) ;
50- const { loading, error, timeseries, reload } = useTimeline (
57+
58+ const { loading, error, startTime, endTime, timeseries, reload } = useTimeline (
5159 filterQueryAsJson ,
5260 [ metric ] ,
5361 nodeType ,
@@ -59,6 +67,40 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
5967 isVisible
6068 ) ;
6169
70+ const anomalyParams = {
71+ sourceId : 'default' ,
72+ startTime,
73+ endTime,
74+ defaultSortOptions : {
75+ direction : 'desc' as const ,
76+ field : 'anomalyScore' as const ,
77+ } ,
78+ defaultPaginationOptions : { pageSize : 100 } ,
79+ } ;
80+
81+ const { metricsHostsAnomalies, getMetricsHostsAnomalies } = useMetricsHostsAnomaliesResults (
82+ anomalyParams
83+ ) ;
84+ const { metricsK8sAnomalies, getMetricsK8sAnomalies } = useMetricsK8sAnomaliesResults (
85+ anomalyParams
86+ ) ;
87+
88+ const getAnomalies = useMemo ( ( ) => {
89+ if ( nodeType === 'host' ) {
90+ return getMetricsHostsAnomalies ;
91+ } else if ( nodeType === 'pod' ) {
92+ return getMetricsK8sAnomalies ;
93+ }
94+ } , [ nodeType , getMetricsK8sAnomalies , getMetricsHostsAnomalies ] ) ;
95+
96+ const anomalies = useMemo ( ( ) => {
97+ if ( nodeType === 'host' ) {
98+ return metricsHostsAnomalies ;
99+ } else if ( nodeType === 'pod' ) {
100+ return metricsK8sAnomalies ;
101+ }
102+ } , [ nodeType , metricsHostsAnomalies , metricsK8sAnomalies ] ) ;
103+
62104 const metricLabel = toMetricOpt ( metric . type ) ?. textLC ;
63105
64106 const chartMetric = {
@@ -104,6 +146,25 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
104146 [ jumpToTime , stopAutoReload ]
105147 ) ;
106148
149+ const anomalyMetricName = useMemo ( ( ) => {
150+ const metricType = metric . type ;
151+ if ( metricType === 'memory' ) {
152+ return 'memory_usage' ;
153+ }
154+ if ( metricType === 'rx' ) {
155+ return 'network_in' ;
156+ }
157+ if ( metricType === 'tx' ) {
158+ return 'network_out' ;
159+ }
160+ } , [ metric ] ) ;
161+
162+ useEffect ( ( ) => {
163+ if ( getAnomalies && anomalyMetricName ) {
164+ getAnomalies ( anomalyMetricName ) ;
165+ }
166+ } , [ getAnomalies , anomalyMetricName ] ) ;
167+
107168 if ( loading ) {
108169 return (
109170 < TimelineContainer >
@@ -130,21 +191,86 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
130191 ) ;
131192 }
132193
194+ function generateAnnotationData ( results : Array < [ number , string [ ] ] > ) : RectAnnotationDatum [ ] {
195+ return results . map ( ( anomaly ) => {
196+ const [ val , influencers ] = anomaly ;
197+ return {
198+ coordinates : {
199+ x0 : val ,
200+ x1 : moment ( val ) . add ( 15 , 'minutes' ) . valueOf ( ) ,
201+ y0 : dataDomain ?. min ,
202+ y1 : dataDomain ?. max ,
203+ } ,
204+ details : influencers . join ( ',' ) ,
205+ } ;
206+ } ) ;
207+ }
208+
133209 return (
134210 < TimelineContainer >
135211 < TimelineHeader >
136- < EuiText >
137- < strong >
138- < FormattedMessage
139- id = "xpack.infra.inventoryTimeline.header"
140- defaultMessage = "Average {metricLabel}"
141- values = { { metricLabel } }
142- />
143- </ strong >
144- </ EuiText >
212+ < EuiFlexItem grow = { true } >
213+ < EuiText >
214+ < strong >
215+ < FormattedMessage
216+ id = "xpack.infra.inventoryTimeline.header"
217+ defaultMessage = "Average {metricLabel}"
218+ values = { { metricLabel } }
219+ />
220+ </ strong >
221+ </ EuiText >
222+ </ EuiFlexItem >
223+ < EuiFlexItem grow = { false } >
224+ < EuiFlexGroup alignItems = { 'center' } >
225+ < EuiFlexItem grow = { false } >
226+ < EuiFlexGroup gutterSize = { 's' } alignItems = { 'center' } >
227+ < EuiFlexItem grow = { false } >
228+ < EuiIcon
229+ color = { getTimelineChartTheme ( isDarkMode ) . crosshair . band . fill }
230+ type = { 'dot' }
231+ />
232+ </ EuiFlexItem >
233+ < EuiFlexItem grow = { false } >
234+ < EuiText size = { 'xs' } >
235+ < FormattedMessage
236+ id = "xpack.infra.inventoryTimeline.header"
237+ defaultMessage = "Average {metricLabel}"
238+ values = { { metricLabel } }
239+ />
240+ </ EuiText >
241+ </ EuiFlexItem >
242+ </ EuiFlexGroup >
243+ </ EuiFlexItem >
244+ < EuiFlexItem grow = { false } >
245+ < EuiFlexGroup gutterSize = { 's' } alignItems = { 'center' } >
246+ < EuiFlexItem
247+ grow = { false }
248+ style = { { backgroundColor : '#D36086' , height : 5 , width : 10 } }
249+ />
250+ < EuiFlexItem >
251+ < EuiText size = { 'xs' } >
252+ < FormattedMessage
253+ id = "xpack.infra.inventoryTimeline.legend.anomalyLabel"
254+ defaultMessage = "Anomaly detected"
255+ />
256+ </ EuiText >
257+ </ EuiFlexItem >
258+ </ EuiFlexGroup >
259+ </ EuiFlexItem >
260+ </ EuiFlexGroup >
261+ </ EuiFlexItem >
145262 </ TimelineHeader >
146263 < TimelineChartContainer >
147264 < Chart >
265+ { anomalies && (
266+ < RectAnnotation
267+ id = { 'anomalies' }
268+ dataValues = { generateAnnotationData (
269+ anomalies . map ( ( a ) => [ a . startTime , a . influencers ] )
270+ ) }
271+ style = { { fill : '#D36086' } }
272+ />
273+ ) }
148274 < MetricExplorerSeriesChart
149275 type = { MetricsExplorerChartType . area }
150276 metric = { chartMetric }
@@ -196,7 +322,7 @@ const TimelineHeader = euiStyled.div`
196322` ;
197323
198324const TimelineChartContainer = euiStyled . div `
199- padding-left: ${ ( props ) => props . theme . eui . paddingSizes . xs } ;
325+ padding-left: ${ ( props ) => props . theme . eui . paddingSizes . xs } ;
200326 width: 100%;
201327 height: 100%;
202328` ;
0 commit comments