Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add missing feature alert if recent feature data is missing #248

183 changes: 113 additions & 70 deletions public/pages/AnomalyCharts/components/FeatureChart/FeatureChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import {
Position,
Settings,
ScaleType,
LineAnnotation,
AnnotationDomainTypes,
} from '@elastic/charts';
import { EuiEmptyPrompt, EuiText, EuiLink, EuiButton } from '@elastic/eui';
import { EuiText, EuiLink, EuiButton, EuiIcon } from '@elastic/eui';
import React, { useState, Fragment } from 'react';
import ContentPanel from '../../../../components/ContentPanel/ContentPanel';
import { useDelayedLoader } from '../../../../hooks/useDelayedLoader';
Expand All @@ -32,14 +34,15 @@ import {
FeatureAttributes,
DateRange,
FEATURE_TYPE,
Schedule,
} from '../../../../models/interfaces';
import { darkModeEnabled } from '../../../../utils/kibanaUtils';
import { prepareDataForChart } from '../../../utils/anomalyResultUtils';
import { CodeModal } from '../../../DetectorConfig/components/CodeModal/CodeModal';
import {
CHART_FIELDS,
FEATURE_CHART_THEME,
} from '../../utils/constants';
prepareDataForChart,
getFeatureMissingDataAnnotations,
} from '../../../utils/anomalyResultUtils';
import { CodeModal } from '../../../DetectorConfig/components/CodeModal/CodeModal';
import { CHART_FIELDS, FEATURE_CHART_THEME } from '../../utils/constants';

interface FeatureChartProps {
feature: FeatureAttributes;
Expand All @@ -54,6 +57,10 @@ interface FeatureChartProps {
featureDataSeriesName: string;
edit?: boolean;
onEdit?(): void;
detectorInterval: Schedule;
showFeatureMissingDataPointAnnotation?: boolean;
detectorEnabledTime?: number;
rawFeatureData: FeatureAggregationData[];
}
const getDisabledChartBackground = () =>
darkModeEnabled() ? '#25262E' : '#F0F0F0';
Expand Down Expand Up @@ -108,6 +115,28 @@ export const FeatureChart = (props: FeatureChartProps) => {
);

const featureData = prepareDataForChart(props.featureData, props.dateRange);

// return undefined if featureMissingDataPointAnnotationStartDate is missing
// OR it is even behind the specified date range
const getFeatureMissingAnnotationDateRange = (
dateRange: DateRange,
featureMissingDataPointAnnotationStartDate?: number
) => {
if (
featureMissingDataPointAnnotationStartDate &&
dateRange.endDate > featureMissingDataPointAnnotationStartDate
) {
return {
startDate: Math.max(
dateRange.startDate,
featureMissingDataPointAnnotationStartDate
),
endDate: dateRange.endDate,
};
}
return undefined;
};

return (
<ContentPanel
title={
Expand All @@ -125,72 +154,86 @@ export const FeatureChart = (props: FeatureChartProps) => {
props.edit ? <EuiButton onClick={props.onEdit}>Edit</EuiButton> : null
}
>
{props.featureData.length > 0 ? (
<div
style={{
height: '200px',
width: '100%',
opacity: showLoader ? 0.2 : 1,
}}
>
<Chart>
<Settings
showLegend
showLegendExtra={false}
//TODO: research more why only set this old property will work.
showLegendDisplayValue={false}
legendPosition={Position.Right}
theme={FEATURE_CHART_THEME}
/>
{props.feature.featureEnabled ? (
<RectAnnotation
dataValues={props.annotations || []}
id="annotations"
style={{
stroke: darkModeEnabled() ? 'red' : '#D5DBDB',
strokeWidth: 1,
opacity: 0.8,
fill: darkModeEnabled() ? 'red' : '#D5DBDB',
}}
/>
) : null}
<Axis
id="left"
title={props.featureDataSeriesName}
position="left"
showGridLines
/>
<Axis id="bottom" position="bottom" tickFormat={timeFormatter} />
<LineSeries
id="featureData"
name={props.featureDataSeriesName}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={CHART_FIELDS.PLOT_TIME}
yAccessors={[CHART_FIELDS.DATA]}
data={featureData}
/>
</Chart>
{showCustomExpression ? (
<CodeModal
title={props.feature.featureName}
subtitle="Custom expression"
code={JSON.stringify(props.feature.aggregationQuery, null, 4)}
getModalVisibilityChange={() => true}
closeModal={() => setShowCustomExpression(false)}
<div
style={{
height: '200px',
width: '100%',
opacity: showLoader ? 0.2 : 1,
}}
>
<Chart>
<Settings
showLegend
showLegendExtra={false}
//TODO: research more why only set this old property will work.
showLegendDisplayValue={false}
legendPosition={Position.Right}
theme={FEATURE_CHART_THEME}
/>
{props.feature.featureEnabled ? (
<RectAnnotation
dataValues={props.annotations || []}
id="annotations"
style={{
stroke: darkModeEnabled() ? 'red' : '#D5DBDB',
strokeWidth: 1,
opacity: 0.8,
fill: darkModeEnabled() ? 'red' : '#D5DBDB',
}}
/>
) : null}
</div>
) : (
<EuiEmptyPrompt
style={{ maxWidth: '45em' }}
body={
<EuiText>
<p>{`There is no data to display for feature ${props.feature.featureName}`}</p>
</EuiText>
}
/>
)}
{props.feature.featureEnabled &&
props.showFeatureMissingDataPointAnnotation &&
props.detectorEnabledTime
? [
<LineAnnotation
id="featureMissingAnnotations"
domainType={AnnotationDomainTypes.XDomain}
dataValues={getFeatureMissingDataAnnotations(
props.showFeatureMissingDataPointAnnotation
? props.rawFeatureData
: props.featureData,
props.detectorInterval.interval,
getFeatureMissingAnnotationDateRange(
props.dateRange,
props.detectorEnabledTime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we will only show missing data alerts from latest detector enabled time? Is it possible we show alerts for data before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, only since latest enabled time. Ideally, we should show alert after detector 1st enabled time, and we should not show data during time period when detector disabled. However, there is no way to keep the 1st enabled time, also no way to keep track of which time period detector is disabled. If detector is disabled/enabled multiple times, it is hard for us to ignore the disabled time periods. That's why we only show alerts since latest enabled time.

),
props.dateRange
)}
marker={<EuiIcon type="alert" />}
style={{
line: { stroke: 'red', strokeWidth: 1, opacity: 0.8 },
}}
/>,
]
: null}
<Axis
id="left"
title={props.featureDataSeriesName}
position="left"
showGridLines
/>
<Axis id="bottom" position="bottom" tickFormat={timeFormatter} />
<LineSeries
id="featureData"
name={props.featureDataSeriesName}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={CHART_FIELDS.PLOT_TIME}
yAccessors={[CHART_FIELDS.DATA]}
data={featureData}
/>
</Chart>
{showCustomExpression ? (
<CodeModal
title={props.feature.featureName}
subtitle="Custom expression"
code={JSON.stringify(props.feature.aggregationQuery, null, 4)}
getModalVisibilityChange={() => true}
closeModal={() => setShowCustomExpression(false)}
/>
) : null}
</div>
</ContentPanel>
);
};
39 changes: 35 additions & 4 deletions public/pages/AnomalyCharts/containers/FeatureBreakDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@

import React from 'react';
import { get } from 'lodash';
import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui';
import {
EuiFlexItem,
EuiFlexGroup,
EuiTitle,
EuiSpacer,
EuiCallOut,
} from '@elastic/eui';
import { FeatureChart } from '../components/FeatureChart/FeatureChart';
import {
Detector,
Expand All @@ -26,6 +32,7 @@ import {
} from '../../../models/interfaces';
import { NoFeaturePrompt } from '../components/FeatureChart/NoFeaturePrompt';
import { focusOnFeatureAccordion } from '../../EditFeatures/utils/helpers';
import moment from 'moment';

interface FeatureBreakDownProps {
title?: string;
Expand All @@ -35,6 +42,9 @@ interface FeatureBreakDownProps {
isLoading: boolean;
dateRange: DateRange;
featureDataSeriesName: string;
showFeatureMissingDataPointAnnotation?: boolean;
rawAnomalyResults?: Anomalies;
isFeatureDataMissing?: boolean;
}

export const FeatureBreakDown = React.memo((props: FeatureBreakDownProps) => {
Expand All @@ -46,11 +56,22 @@ export const FeatureBreakDown = React.memo((props: FeatureBreakDownProps) => {
<EuiTitle size="s" className="preview-title">
<h4>{props.title}</h4>
</EuiTitle>
<EuiSpacer size="s" />
<EuiSpacer size="s" />
</EuiFlexItem>
</EuiFlexGroup>
) : null}

{props.showFeatureMissingDataPointAnnotation &&
props.detector.enabledTime &&
props.isFeatureDataMissing ? (
<EuiCallOut
title={`Missing data is only shown since last enabled time: ${moment(
props.detector.enabledTime
).format('MM/DD/YY h:mm A')}`}
color={'warning'}
iconType={'alert'}
style={{ marginBottom: '20px' }}
/>
) : null}
{get(props, 'detector.featureAttributes', []).map(
(feature: FeatureAttributes, index: number) => (
<React.Fragment key={`${feature.featureName}-${feature.featureId}`}>
Expand All @@ -61,6 +82,11 @@ export const FeatureBreakDown = React.memo((props: FeatureBreakDownProps) => {
`anomaliesResult.featureData.${feature.featureId}`,
[]
)}
rawFeatureData={get(
props,
`rawAnomalyResults.featureData.${feature.featureId}`,
[]
)}
annotations={props.annotations}
isLoading={props.isLoading}
dateRange={props.dateRange}
Expand Down Expand Up @@ -97,8 +123,13 @@ export const FeatureBreakDown = React.memo((props: FeatureBreakDownProps) => {
onEdit={() => {
focusOnFeatureAccordion(index);
}}
detectorInterval={props.detector.detectionInterval.period}
showFeatureMissingDataPointAnnotation={
props.showFeatureMissingDataPointAnnotation
}
detectorEnabledTime={props.detector.enabledTime}
/>
<EuiSpacer size='m'/>
<EuiSpacer size="m" />
</React.Fragment>
)
)}
Expand Down
Loading