Skip to content

Commit

Permalink
Support multiple category fields (#66)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Sep 1, 2021
1 parent a15ac4c commit b4c7224
Show file tree
Hide file tree
Showing 30 changed files with 2,121 additions and 1,045 deletions.
10 changes: 0 additions & 10 deletions public/models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,6 @@ export type DetectorListItem = {
enabledTime?: number;
};

export type HistoricalDetectorListItem = {
id: string;
name: string;
curState: DETECTOR_STATE;
indices: string[];
totalAnomalies: number;
dataStartTime: number;
dataEndTime: number;
};

export type EntityData = {
name: string;
value: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ interface FeatureChartProps {
showFeatureMissingDataPointAnnotation?: boolean;
detectorEnabledTime?: number;
rawFeatureData: FeatureAggregationData[];
titlePrefix?: string;
}

export const FeatureChart = (props: FeatureChartProps) => {
Expand Down Expand Up @@ -165,10 +164,9 @@ export const FeatureChart = (props: FeatureChartProps) => {
return (
<ContentPanel
title={
(props.titlePrefix ? props.titlePrefix + ' - ' : '') +
(props.feature.featureEnabled
props.feature.featureEnabled
? props.feature.featureName
: `${props.feature.featureName} (disabled)`)
: `${props.feature.featureName} (disabled)`
}
bodyStyles={
!props.feature.featureEnabled
Expand Down
33 changes: 21 additions & 12 deletions public/pages/AnomalyCharts/containers/AnomaliesChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
EuiSuperDatePicker,
EuiTitle,
} from '@elastic/eui';
import { get } from 'lodash';
import { get, orderBy } from 'lodash';
import moment, { DurationInputArg2 } from 'moment';
import React, { useState } from 'react';
import { EntityAnomalySummaries } from '../../../../server/models/interfaces';
Expand All @@ -58,6 +58,7 @@ import {
getConfidenceWording,
getFeatureBreakdownWording,
getFeatureDataWording,
getHCTitle,
} from '../utils/anomalyChartUtils';
import {
DATE_PICKER_QUICK_OPTIONS,
Expand All @@ -67,7 +68,7 @@ import { AnomalyOccurrenceChart } from './AnomalyOccurrenceChart';
import { FeatureBreakDown } from './FeatureBreakDown';
import { convertTimestampToString } from '../../../utils/utils';

interface AnomaliesChartProps {
export interface AnomaliesChartProps {
onDateRangeChange(
startDate: number,
endDate: number,
Expand Down Expand Up @@ -245,9 +246,13 @@ export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
<AnomalyHeatmapChart
detectorId={get(props.detector, 'id', '')}
detectorName={get(props.detector, 'name', '')}
detectorTaskProgress={get(
props.detector,
'taskProgress',
0
)}
isHistorical={props.isHistorical}
dateRange={props.dateRange}
//@ts-ignore
title={props.detectorCategoryField[0]}
anomalies={anomalies}
isLoading={props.isLoading}
showAlerts={props.showAlerts}
Expand All @@ -266,6 +271,12 @@ export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
onDisplayOptionChanged={props.onDisplayOptionChanged}
heatmapDisplayOption={props.heatmapDisplayOption}
isNotSample={props.isNotSample}
// Category fields in HC results are always sorted alphabetically. To make all chart
// wording consistent with the returned results, we sort the given category
// fields in alphabetical order as well.
categoryField={orderBy(props.detectorCategoryField, [
(categoryField) => categoryField.toLowerCase(),
])}
/>,
props.isNotSample !== true
? [
Expand All @@ -284,15 +295,13 @@ export const AnomaliesChart = React.memo((props: AnomaliesChartProps) => {
<EuiSpacer size="s" />
<EuiTitle size="s">
<h3>
{`${get(
props,
'detectorCategoryField.0'
)} `}
<b>
{
props.selectedHeatmapCell
?.entityValue
}
{props.selectedHeatmapCell
? getHCTitle(
props.selectedHeatmapCell
.entityList
)
: '-'}
</b>
</h3>
</EuiTitle>
Expand Down
142 changes: 99 additions & 43 deletions public/pages/AnomalyCharts/containers/AnomalyHeatmapChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
* permissions and limitations under the License.
*/

import React, { useState } from 'react';

import React, { useState, useEffect } from 'react';
import moment from 'moment';
import Plotly, { PlotData } from 'plotly.js-dist';
import plotComponentFactory from 'react-plotly.js/factory';
Expand Down Expand Up @@ -54,13 +53,25 @@ import {
filterHeatmapPlotDataByY,
getEntitytAnomaliesHeatmapData,
} from '../utils/anomalyChartUtils';
import { MIN_IN_MILLI_SECS } from '../../../../server/utils/constants';
import { EntityAnomalySummaries } from '../../../../server/models/interfaces';
import {
MIN_IN_MILLI_SECS,
ENTITY_LIST_DELIMITER,
} from '../../../../server/utils/constants';
import {
EntityAnomalySummaries,
Entity,
} from '../../../../server/models/interfaces';
import { HEATMAP_CHART_Y_AXIS_WIDTH } from '../utils/constants';
import {
convertToEntityList,
convertToCategoryFieldString,
} from '../../utils/anomalyResultUtils';

interface AnomalyHeatmapChartProps {
title: string;
detectorId: string;
detectorName: string;
detectorTaskProgress?: number;
isHistorical?: boolean;
anomalies?: any[];
dateRange: DateRange;
isLoading: boolean;
Expand All @@ -72,11 +83,13 @@ interface AnomalyHeatmapChartProps {
heatmapDisplayOption?: HeatmapDisplayOption;
entityAnomalySummaries?: EntityAnomalySummaries[];
isNotSample?: boolean;
categoryField?: string[];
}

export interface HeatmapCell {
dateRange: DateRange;
entityValue: string;
entityList: Entity[];
modelId?: string;
}

export interface HeatmapDisplayOption {
Expand Down Expand Up @@ -123,14 +136,15 @@ export const AnomalyHeatmapChart = React.memo(
//@ts-ignore
individualEntities = inputHeatmapData[0].y.filter(
//@ts-ignore
(entityValue) => entityValue && entityValue.trim().length > 0
(entityListAsString) =>
entityListAsString && entityListAsString.trim().length > 0
);
}
const individualEntityOptions = [] as any[];
//@ts-ignore
individualEntities.forEach((entityValue) => {
individualEntities.forEach((entityListAsString: string) => {
individualEntityOptions.push({
label: entityValue,
label: entityListAsString.replace(ENTITY_LIST_DELIMITER, ', '),
});
});

Expand Down Expand Up @@ -165,7 +179,7 @@ export const AnomalyHeatmapChart = React.memo(
getEntitytAnomaliesHeatmapData(
props.dateRange,
props.entityAnomalySummaries,
props.heatmapDisplayOption.entityOption.value
props.heatmapDisplayOption?.entityOption.value
)
: // use anomalies data in case of sample result
getAnomaliesHeatmapData(
Expand All @@ -184,7 +198,7 @@ export const AnomalyHeatmapChart = React.memo(
AnomalyHeatmapSortType
>(
props.isNotSample
? props.heatmapDisplayOption.sortType
? props.heatmapDisplayOption?.sortType
: SORT_BY_FIELD_OPTIONS[0].value
);

Expand Down Expand Up @@ -215,9 +229,25 @@ export const AnomalyHeatmapChart = React.memo(
return false;
};

// Custom hook to refresh all of the heatmap data when running a historical task
useEffect(() => {
if (props.isHistorical) {
const updateHeatmapPlotData = getAnomaliesHeatmapData(
props.anomalies,
props.dateRange,
sortByFieldValue,
get(COMBINED_OPTIONS.options[0], 'value')
);
setOriginalHeatmapData(updateHeatmapPlotData);
setHeatmapData(updateHeatmapPlotData);
setNumEntities(updateHeatmapPlotData[0].y.length);
setEntityViewOptions(getViewEntityOptions(updateHeatmapPlotData));
}
}, [props.detectorTaskProgress]);

const handleHeatmapClick = (event: Plotly.PlotMouseEvent) => {
const selectedCellIndices = get(event, 'points[0].pointIndex', []);
const selectedEntity = get(event, 'points[0].y', '');
const selectedEntityString = get(event, 'points[0].y', '');
if (!isEmpty(selectedCellIndices)) {
let anomalyCount = get(event, 'points[0].text', 0);
if (
Expand Down Expand Up @@ -262,7 +292,11 @@ export const AnomalyHeatmapChart = React.memo(
startDate: selectedStartDate,
endDate: selectedEndDate,
},
entityValue: selectedEntity,
entityList: convertToEntityList(
selectedEntityString,
get(props, 'categoryField', []),
ENTITY_LIST_DELIMITER
),
} as HeatmapCell);
}
}
Expand Down Expand Up @@ -345,7 +379,7 @@ export const AnomalyHeatmapChart = React.memo(

setNumEntities(nonCombinedOptions.length);
const selectedYs = nonCombinedOptions.map((option) =>
get(option, 'label', '')
get(option, 'label', '').replace(', ', ENTITY_LIST_DELIMITER)
);

let selectedHeatmapData = filterHeatmapPlotDataByY(
Expand Down Expand Up @@ -407,40 +441,53 @@ export const AnomalyHeatmapChart = React.memo(
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup style={{ padding: '0px' }}>
<EuiFlexItem grow={false} style={{ minWidth: '80px' }}>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
<EuiText textAlign="right">
<h4>{props.title}</h4>
</EuiText>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem style={{ paddingLeft: '5px' }}>
<EuiFlexGroup
style={{ padding: '0px' }}
justifyContent="spaceBetween"
>
<EuiFlexItem grow={false} style={{ marginLeft: '0px' }}>
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem style={{ minWidth: 300 }}>
<EuiComboBox
placeholder="Select options"
options={entityViewOptions}
selectedOptions={currentViewOptions}
onChange={(selectedOptions) =>
handleViewEntityOptionsChange(selectedOptions)
}
/>
</EuiFlexItem>
<EuiFlexItem style={{ minWidth: 150 }}>
<EuiSuperSelect
options={SORT_BY_FIELD_OPTIONS}
valueOfSelected={sortByFieldValue}
onChange={(value) => handleSortByFieldChange(value)}
hasDividers
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexGroup
direction="column"
justifyContent="spaceBetween"
style={{ padding: '12px', marginBottom: '6px' }}
>
<EuiFlexItem style={{ marginBottom: '0px' }}>
<EuiText>
<h4>
View by:&nbsp;
<b>
{convertToCategoryFieldString(
get(props, 'categoryField', []) as string[],
', '
)}
</b>
</h4>
</EuiText>
</EuiFlexItem>

<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem style={{ minWidth: 300 }}>
<EuiComboBox
placeholder="Select options"
options={entityViewOptions}
selectedOptions={currentViewOptions}
onChange={(selectedOptions) =>
handleViewEntityOptionsChange(selectedOptions)
}
/>
</EuiFlexItem>
<EuiFlexItem style={{ minWidth: 150 }}>
<EuiSuperSelect
options={SORT_BY_FIELD_OPTIONS}
valueOfSelected={sortByFieldValue}
onChange={(value) => handleSortByFieldChange(value)}
hasDividers
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
Expand Down Expand Up @@ -542,6 +589,15 @@ export const AnomalyHeatmapChart = React.memo(
showline: true,
showgrid: false,
fixedrange: true,
automargin: true,
tickmode: 'array',
tickvals: heatmapData[0].y,
ticktext: heatmapData[0].y.map((label: string) =>
label.length <= HEATMAP_CHART_Y_AXIS_WIDTH
? label
: label.substring(0, HEATMAP_CHART_Y_AXIS_WIDTH - 3) +
'...'
),
},
margin: {
l: 100,
Expand Down
Loading

0 comments on commit b4c7224

Please sign in to comment.