Skip to content

Commit

Permalink
[ML] Handle Empty Partition Field Values in Single Metric Viewer (ela…
Browse files Browse the repository at this point in the history
…stic#61649)

* [ML] WIP support empty partition fields values

* [ML] support empty field in anomaly table

* [ML] remove comments

* [ML] fix context chart

* [ML] rename empty field label, render as italic

* [ML] rename empty field label

* [ML] fix focus chart

* [ML] add time range capping for fields_service.ts

* [ML] empty string labels in anomaly explorer
  • Loading branch information
darnautov committed Mar 31, 2020
1 parent 7ee4743 commit 25f880e
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import React from 'react';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EMPTY_FIELD_VALUE_LABEL } from '../../timeseriesexplorer/components/entity_control/entity_control';
import { MLCATEGORY } from '../../../../common/constants/field_types';

function getAddFilter({ entityName, entityValue, filter }) {
return (
Expand Down Expand Up @@ -68,7 +70,11 @@ export const EntityCell = function EntityCell({
filter,
wrapText = false,
}) {
const valueText = entityName !== 'mlcategory' ? entityValue : `mlcategory ${entityValue}`;
let valueText = entityValue === '' ? <i>{EMPTY_FIELD_VALUE_LABEL}</i> : entityValue;
if (entityName === MLCATEGORY) {
valueText = `${MLCATEGORY} ${valueText}`;
}

const textStyle = { maxWidth: '100%' };
const textWrapperClass = wrapText ? 'field-value-long' : 'field-value-short';
const shouldDisplayIcons =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { mlChartTooltipService } from '../components/chart_tooltip/chart_tooltip
import { ALLOW_CELL_RANGE_SELECTION, dragSelect$ } from './explorer_dashboard_service';
import { DRAG_SELECT_ACTION } from './explorer_constants';
import { i18n } from '@kbn/i18n';
import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control';

const SCSS = {
mlDragselectDragging: 'mlDragselectDragging',
Expand Down Expand Up @@ -309,6 +310,7 @@ export class ExplorerSwimlane extends React.Component {
return function(lane) {
const bucketScore = getBucketScore(lane, time);
if (bucketScore !== 0) {
lane = lane === '' ? EMPTY_FIELD_VALUE_LABEL : lane;
cellMouseover(this, lane, bucketScore, i, time);
}
};
Expand Down Expand Up @@ -376,7 +378,7 @@ export class ExplorerSwimlane extends React.Component {
values: { label: mlEscape(label) },
});
} else {
return mlEscape(label);
return label === '' ? `<i>${EMPTY_FIELD_VALUE_LABEL}</i>` : mlEscape(label);
}
})
.on('click', () => {
Expand All @@ -393,7 +395,7 @@ export class ExplorerSwimlane extends React.Component {
{ skipHeader: true },
{
label: swimlaneData.fieldName,
value,
value: value === '' ? EMPTY_FIELD_VALUE_LABEL : value,
seriesIdentifier: { key: value },
valueAccessor: 'fieldName',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1259,39 +1259,13 @@ export function getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, lates
},
{ term: { job_id: jobId } },
];
const shouldCriteria = [];

_.each(criteriaFields, criteria => {
if (criteria.fieldValue.length !== 0) {
mustCriteria.push({
term: {
[criteria.fieldName]: criteria.fieldValue,
},
});
} else {
// Add special handling for blank entity field values, checking for either
// an empty string or the field not existing.
const emptyFieldCondition = {
bool: {
must: [
{
term: {},
},
],
},
};
emptyFieldCondition.bool.must[0].term[criteria.fieldName] = '';
shouldCriteria.push(emptyFieldCondition);
shouldCriteria.push({
bool: {
must_not: [
{
exists: { field: criteria.fieldName },
},
],
},
});
}
mustCriteria.push({
term: {
[criteria.fieldName]: criteria.fieldValue,
},
});
});

ml.esSearch({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
EuiFormRow,
EuiToolTip,
} from '@elastic/eui';
import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option';

export interface Entity {
fieldName: string;
Expand All @@ -29,15 +30,22 @@ interface EntityControlProps {
isLoading: boolean;
onSearchChange: (entity: Entity, queryTerm: string) => void;
forceSelection: boolean;
options: EuiComboBoxOptionOption[];
options: Array<EuiComboBoxOptionOption<string>>;
}

interface EntityControlState {
selectedOptions: EuiComboBoxOptionOption[] | undefined;
selectedOptions: Array<EuiComboBoxOptionOption<string>> | undefined;
isLoading: boolean;
options: EuiComboBoxOptionOption[] | undefined;
options: Array<EuiComboBoxOptionOption<string>> | undefined;
}

export const EMPTY_FIELD_VALUE_LABEL = i18n.translate(
'xpack.ml.timeSeriesExplorer.emptyPartitionFieldLabel.',
{
defaultMessage: '"" (empty string)',
}
);

export class EntityControl extends Component<EntityControlProps, EntityControlState> {
inputRef: any;

Expand All @@ -53,16 +61,18 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt

const { fieldValue } = entity;

let selectedOptionsUpdate: EuiComboBoxOptionOption[] | undefined = selectedOptions;
let selectedOptionsUpdate: Array<EuiComboBoxOptionOption<string>> | undefined = selectedOptions;
if (
(selectedOptions === undefined && fieldValue.length > 0) ||
(selectedOptions === undefined && fieldValue !== null) ||
(Array.isArray(selectedOptions) &&
// @ts-ignore
selectedOptions[0].label !== fieldValue &&
fieldValue.length > 0)
selectedOptions[0].value !== fieldValue &&
fieldValue !== null)
) {
selectedOptionsUpdate = [{ label: fieldValue }];
} else if (Array.isArray(selectedOptions) && fieldValue.length === 0) {
selectedOptionsUpdate = [
{ label: fieldValue === '' ? EMPTY_FIELD_VALUE_LABEL : fieldValue, value: fieldValue },
];
} else if (Array.isArray(selectedOptions) && fieldValue === null) {
selectedOptionsUpdate = undefined;
}

Expand All @@ -84,14 +94,14 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
}
}

onChange = (selectedOptions: EuiComboBoxOptionOption[]) => {
onChange = (selectedOptions: Array<EuiComboBoxOptionOption<string>>) => {
const options = selectedOptions.length > 0 ? selectedOptions : undefined;
this.setState({
selectedOptions: options,
});

const fieldValue =
Array.isArray(options) && options[0].label.length > 0 ? options[0].label : '';
Array.isArray(options) && options[0].value !== null ? options[0].value : null;
this.props.entityFieldValueChanged(this.props.entity, fieldValue);
};

Expand All @@ -103,6 +113,11 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
this.props.onSearchChange(this.props.entity, searchValue);
};

renderOption = (option: EuiSelectableOption) => {
const { label } = option;
return label === EMPTY_FIELD_VALUE_LABEL ? <i>{label}</i> : label;
};

render() {
const { entity, forceSelection } = this.props;
const { isLoading, options, selectedOptions } = this.state;
Expand All @@ -126,6 +141,7 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
onChange={this.onChange}
onSearchChange={this.onSearchChange}
isClearable={false}
renderOption={this.renderOption}
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function getChartDetails(
obj.results.functionLabel = functionLabel;

const blankEntityFields = _.filter(entityFields, entity => {
return entity.fieldValue.length === 0;
return entity.fieldValue === null;
});

// Look to see if any of the entity fields have defined values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
processRecordScoreResults,
getFocusData,
} from './timeseriesexplorer_utils';
import { EMPTY_FIELD_VALUE_LABEL } from './components/entity_control/entity_control';

// Used to indicate the chart is being plotted across
// all partition field values, where the cardinality of the field cannot be
Expand All @@ -94,7 +95,7 @@ function getEntityControlOptions(fieldValues) {
fieldValues.sort();

return fieldValues.map(value => {
return { label: value };
return { label: value === '' ? EMPTY_FIELD_VALUE_LABEL : value, value };
});
}

Expand Down Expand Up @@ -192,7 +193,7 @@ export class TimeSeriesExplorer extends React.Component {
getFieldNamesWithEmptyValues = () => {
const latestEntityControls = this.getControlsForDetector();
return latestEntityControls
.filter(({ fieldValue }) => !fieldValue)
.filter(({ fieldValue }) => fieldValue === null)
.map(({ fieldName }) => fieldName);
};

Expand Down Expand Up @@ -249,7 +250,7 @@ export class TimeSeriesExplorer extends React.Component {
if (operator === '+' && entity.fieldValue !== value) {
resultValue = value;
} else if (operator === '-' && entity.fieldValue === value) {
resultValue = '';
resultValue = null;
} else {
return;
}
Expand Down Expand Up @@ -302,7 +303,7 @@ export class TimeSeriesExplorer extends React.Component {
focusAggregationInterval,
selectedForecastId,
modelPlotEnabled,
entityControls.filter(entity => entity.fieldValue.length > 0),
entityControls.filter(entity => entity.fieldValue !== null),
searchBounds,
selectedJob,
TIME_FIELD_NAME
Expand Down Expand Up @@ -576,7 +577,7 @@ export class TimeSeriesExplorer extends React.Component {
};

const nonBlankEntities = entityControls.filter(entity => {
return entity.fieldValue.length > 0;
return entity.fieldValue !== null;
});

if (
Expand Down Expand Up @@ -739,15 +740,15 @@ export class TimeSeriesExplorer extends React.Component {
const overFieldName = get(detector, 'over_field_name');
const byFieldName = get(detector, 'by_field_name');
if (partitionFieldName !== undefined) {
const partitionFieldValue = get(entitiesState, partitionFieldName, '');
const partitionFieldValue = get(entitiesState, partitionFieldName, null);
entities.push({
fieldType: 'partition_field',
fieldName: partitionFieldName,
fieldValue: partitionFieldValue,
});
}
if (overFieldName !== undefined) {
const overFieldValue = get(entitiesState, overFieldName, '');
const overFieldValue = get(entitiesState, overFieldName, null);
entities.push({
fieldType: 'over_field',
fieldName: overFieldName,
Expand All @@ -761,7 +762,7 @@ export class TimeSeriesExplorer extends React.Component {
// TODO - metric data can be filtered by this field, so should only exclude
// from filter for the anomaly records.
if (byFieldName !== undefined && overFieldName === undefined) {
const byFieldValue = get(entitiesState, byFieldName, '');
const byFieldValue = get(entitiesState, byFieldName, null);
entities.push({ fieldType: 'by_field', fieldName: byFieldName, fieldValue: byFieldValue });
}

Expand All @@ -775,7 +776,7 @@ export class TimeSeriesExplorer extends React.Component {
*/
getCriteriaFields(detectorIndex, entities) {
// Only filter on the entity if the field has a value.
const nonBlankEntities = entities.filter(entity => entity.fieldValue.length > 0);
const nonBlankEntities = entities.filter(entity => entity.fieldValue !== null);
return [
{
fieldName: 'detector_index',
Expand Down Expand Up @@ -1150,7 +1151,7 @@ export class TimeSeriesExplorer extends React.Component {
</EuiFlexItem>
{entityControls.map(entity => {
const entityKey = `${entity.fieldName}`;
const forceSelection = !hasEmptyFieldValues && !entity.fieldValue;
const forceSelection = !hasEmptyFieldValues && entity.fieldValue === null;
hasEmptyFieldValues = !hasEmptyFieldValues && forceSelection;
return (
<EntityControl
Expand Down
Loading

0 comments on commit 25f880e

Please sign in to comment.