11import color from 'color' ;
2- import type { MarkAreaComponentOption , YAXisComponentOption } from 'echarts' ;
2+ import type { YAXisComponentOption } from 'echarts' ;
33import moment from 'moment-timezone' ;
44
55import type { AreaChartProps , AreaChartSeries } from 'sentry/components/charts/areaChart' ;
@@ -16,12 +16,9 @@ import {getCrashFreeRateSeries} from 'sentry/utils/sessions';
1616import { lightTheme as theme } from 'sentry/utils/theme' ;
1717import type { MetricRule , Trigger } from 'sentry/views/alerts/rules/metric/types' ;
1818import { AlertRuleTriggerType , Dataset } from 'sentry/views/alerts/rules/metric/types' ;
19+ import { getAnomalyMarkerSeries } from 'sentry/views/alerts/rules/metric/utils/anomalyChart' ;
1920import type { Anomaly , Incident } from 'sentry/views/alerts/types' ;
20- import {
21- AnomalyType ,
22- IncidentActivityType ,
23- IncidentStatus ,
24- } from 'sentry/views/alerts/types' ;
21+ import { IncidentActivityType , IncidentStatus } from 'sentry/views/alerts/types' ;
2522import {
2623 ALERT_CHART_MIN_MAX_BUFFER ,
2724 alertAxisFormatter ,
@@ -140,48 +137,6 @@ function createIncidentSeries(
140137 } ;
141138}
142139
143- function createAnomalyMarkerSeries (
144- lineColor : string ,
145- timestamp : string
146- ) : AreaChartSeries {
147- const formatter = ( { value} : any ) => {
148- const time = formatTooltipDate ( moment ( value ) , 'MMM D, YYYY LT' ) ;
149- return [
150- `<div class="tooltip-series"><div>` ,
151- `</div>Anomaly Detected</div>` ,
152- `<div class="tooltip-footer">${ time } </div>` ,
153- '<div class="tooltip-arrow"></div>' ,
154- ] . join ( '' ) ;
155- } ;
156-
157- return {
158- seriesName : 'Anomaly Line' ,
159- type : 'line' ,
160- markLine : MarkLine ( {
161- silent : false ,
162- lineStyle : { color : lineColor , type : 'dashed' } ,
163- label : {
164- silent : true ,
165- show : false ,
166- } ,
167- data : [
168- {
169- xAxis : timestamp ,
170- } ,
171- ] ,
172- tooltip : {
173- formatter,
174- } ,
175- } ) ,
176- data : [ ] ,
177- tooltip : {
178- trigger : 'item' ,
179- alwaysShowContent : true ,
180- formatter,
181- } ,
182- } ;
183- }
184-
185140export type MetricChartData = {
186141 rule : MetricRule ;
187142 timeseriesData : Series [ ] ;
@@ -263,8 +218,11 @@ export function getMetricAlertChartOption({
263218 ) / ALERT_CHART_MIN_MAX_BUFFER
264219 )
265220 : 0 ;
266- const firstPoint = new Date ( dataArr [ 0 ] ?. name ) . getTime ( ) ;
267- const lastPoint = new Date ( dataArr [ dataArr . length - 1 ] ?. name ) . getTime ( ) ;
221+ const startDate = new Date ( dataArr [ 0 ] ?. name ) ;
222+ const endDate =
223+ dataArr . length > 1 ? new Date ( dataArr [ dataArr . length - 1 ] ?. name ) : new Date ( ) ;
224+ const firstPoint = startDate . getTime ( ) ;
225+ const lastPoint = endDate . getTime ( ) ;
268226 const totalDuration = lastPoint - firstPoint ;
269227 let waitingForDataDuration = 0 ;
270228 let criticalDuration = 0 ;
@@ -403,77 +361,8 @@ export function getMetricAlertChartOption({
403361 } ) ;
404362 }
405363 if ( anomalies ) {
406- const anomalyBlocks : MarkAreaComponentOption [ 'data' ] = [ ] ;
407- let start : string | undefined ;
408- let end : string | undefined ;
409- anomalies
410- . filter ( anomalyts => {
411- const ts = new Date ( anomalyts . timestamp ) . getTime ( ) ;
412- return firstPoint < ts && ts < lastPoint ;
413- } )
414- . forEach ( anomalyts => {
415- const { anomaly, timestamp} = anomalyts ;
416-
417- if (
418- [ AnomalyType . high , AnomalyType . low ] . includes ( anomaly . anomaly_type as string )
419- ) {
420- if ( ! start ) {
421- // If this is the start of an anomaly, set start
422- start = new Date ( timestamp ) . toISOString ( ) ;
423- }
424- // as long as we have an valid anomaly type - continue tracking until we've hit the end
425- end = new Date ( timestamp ) . toISOString ( ) ;
426- } else {
427- if ( start && end ) {
428- // If we've hit a non-anomaly type, push the block
429- anomalyBlocks . push ( [
430- {
431- xAxis : start ,
432- } ,
433- {
434- xAxis : end ,
435- } ,
436- ] ) ;
437- // Create a marker line for the start of the anomaly
438- series . push ( createAnomalyMarkerSeries ( theme . purple300 , start ) ) ;
439- }
440- // reset the start/end to capture the next anomaly block
441- start = undefined ;
442- end = undefined ;
443- }
444- } ) ;
445- if ( start && end ) {
446- // push in the last block
447- // Create a marker line for the start of the anomaly
448- series . push ( createAnomalyMarkerSeries ( theme . purple300 , start ) ) ;
449- anomalyBlocks . push ( [
450- {
451- xAxis : start ,
452- } ,
453- {
454- xAxis : end ,
455- } ,
456- ] ) ;
457- }
458-
459- // NOTE: if timerange is too small - highlighted area will not be visible
460- // Possibly provide a minimum window size if the time range is too large?
461- series . push ( {
462- seriesName : '' ,
463- name : 'Anomaly' ,
464- type : 'line' ,
465- smooth : true ,
466- data : [ ] ,
467- markArea : {
468- itemStyle : {
469- color : 'rgba(255, 173, 177, 0.4)' ,
470- } ,
471- silent : true , // potentially don't make this silent if we want to render the `anomaly detected` in the tooltip
472- data : anomalyBlocks ,
473- } ,
474- } ) ;
364+ series . push ( ...getAnomalyMarkerSeries ( anomalies , { startDate, endDate} ) ) ;
475365 }
476-
477366 let maxThresholdValue = 0 ;
478367 if ( ! rule . comparisonDelta && warningTrigger ?. alertThreshold ) {
479368 const { alertThreshold} = warningTrigger ;
0 commit comments