mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
This commit is contained in:
parent
06e4388b95
commit
1687a660bf
4 changed files with 70 additions and 30 deletions
|
@ -171,7 +171,7 @@ export function getColumns(
|
|||
}),
|
||||
render: (actual, item) => {
|
||||
const fieldFormat = mlFieldFormatService.getFieldFormat(item.jobId, item.source.detector_index);
|
||||
return formatValue(item.actual, item.source.function, fieldFormat);
|
||||
return formatValue(item.actual, item.source.function, fieldFormat, item.source);
|
||||
},
|
||||
sortable: true
|
||||
});
|
||||
|
@ -185,7 +185,7 @@ export function getColumns(
|
|||
}),
|
||||
render: (typical, item) => {
|
||||
const fieldFormat = mlFieldFormatService.getFieldFormat(item.jobId, item.source.detector_index);
|
||||
return formatValue(item.typical, item.source.function, fieldFormat);
|
||||
return formatValue(item.typical, item.source.function, fieldFormat, item.source);
|
||||
},
|
||||
sortable: true
|
||||
});
|
||||
|
|
|
@ -152,7 +152,7 @@ function getDetailsItems(anomaly, examples, filter) {
|
|||
title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.actualTitle', {
|
||||
defaultMessage: 'actual',
|
||||
}),
|
||||
description: formatValue(anomaly.actual, source.function)
|
||||
description: formatValue(anomaly.actual, source.function, undefined, source)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ function getDetailsItems(anomaly, examples, filter) {
|
|||
title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.typicalTitle', {
|
||||
defaultMessage: 'typical',
|
||||
}),
|
||||
description: formatValue(anomaly.typical, source.function)
|
||||
description: formatValue(anomaly.typical, source.function, undefined, source)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,23 +7,55 @@
|
|||
|
||||
|
||||
import expect from 'expect.js';
|
||||
import moment from 'moment';
|
||||
import moment from 'moment-timezone';
|
||||
import { formatValue } from '../format_value';
|
||||
|
||||
describe('ML - formatValue formatter', () => {
|
||||
const timeOfWeekRecord = {
|
||||
job_id: 'gallery_time_of_week',
|
||||
result_type: 'record',
|
||||
probability: 0.012818,
|
||||
record_score: 53.55134,
|
||||
bucket_span: 900,
|
||||
detector_index: 0,
|
||||
timestamp: 1530155700000,
|
||||
by_field_name: 'clientip',
|
||||
by_field_value: '65.55.215.39',
|
||||
function: 'time_of_week',
|
||||
function_description: 'time'
|
||||
};
|
||||
|
||||
// Just check the return value is in the expected format, and
|
||||
// not the exact value as this will be timezone specific.
|
||||
const timeOfDayRecord = {
|
||||
job_id: 'gallery_time_of_day',
|
||||
result_type: 'record',
|
||||
probability: 0.012818,
|
||||
record_score: 97.94245,
|
||||
bucket_span: 900,
|
||||
detector_index: 0,
|
||||
timestamp: 1517472900000,
|
||||
by_field_name: 'clientip',
|
||||
by_field_value: '157.56.93.83',
|
||||
function: 'time_of_day',
|
||||
function_description: 'time'
|
||||
};
|
||||
|
||||
// Set timezone to US/Eastern for time_of_day and time_of_week tests.
|
||||
beforeEach(() => {
|
||||
moment.tz.setDefault('US/Eastern');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
moment.tz.setDefault('Browser');
|
||||
});
|
||||
|
||||
// For time_of_day and time_of_week test values which are offsets in seconds
|
||||
// from UTC start of week / day are formatted correctly using the test timezone.
|
||||
it('correctly formats time_of_week value from numeric input', () => {
|
||||
const formattedValue = formatValue(1483228800, 'time_of_week');
|
||||
const result = moment(formattedValue, 'ddd hh:mm', true).isValid();
|
||||
expect(result).to.be(true);
|
||||
expect(formatValue(359739, 'time_of_week', undefined, timeOfWeekRecord)).to.be('Wed 23:55');
|
||||
});
|
||||
|
||||
it('correctly formats time_of_day value from numeric input', () => {
|
||||
const formattedValue = formatValue(1483228800, 'time_of_day');
|
||||
const result = moment(formattedValue, 'hh:mm', true).isValid();
|
||||
expect(result).to.be(true);
|
||||
expect(formatValue(73781, 'time_of_day', undefined, timeOfDayRecord)).to.be('15:29');
|
||||
});
|
||||
|
||||
it('correctly formats number values from numeric input', () => {
|
||||
|
@ -37,15 +69,11 @@ describe('ML - formatValue formatter', () => {
|
|||
});
|
||||
|
||||
it('correctly formats time_of_week value from array input', () => {
|
||||
const formattedValue = formatValue([1483228800], 'time_of_week');
|
||||
const result = moment(formattedValue, 'ddd hh:mm', true).isValid();
|
||||
expect(result).to.be(true);
|
||||
expect(formatValue([359739], 'time_of_week', undefined, timeOfWeekRecord)).to.be('Wed 23:55');
|
||||
});
|
||||
|
||||
it('correctly formats time_of_day value from array input', () => {
|
||||
const formattedValue = formatValue([1483228800], 'time_of_day');
|
||||
const result = moment(formattedValue, 'hh:mm', true).isValid();
|
||||
expect(result).to.be(true);
|
||||
expect(formatValue([73781], 'time_of_day', undefined, timeOfDayRecord)).to.be('15:29');
|
||||
});
|
||||
|
||||
it('correctly formats number values from array input', () => {
|
||||
|
|
|
@ -24,42 +24,54 @@ const SIGFIGS_IF_ROUNDING = 3; // Number of sigfigs to use for values < 10
|
|||
// Formats the value of an actual or typical field from a machine learning anomaly record.
|
||||
// mlFunction is the 'function' field from the ML record containing what the user entered e.g. 'high_count',
|
||||
// (as opposed to the 'function_description' field which holds an ML-built display hint for the function e.g. 'count'.
|
||||
export function formatValue(value, mlFunction, fieldFormat) {
|
||||
// If a Kibana fieldFormat is not supplied, will fall back to default
|
||||
// formatting depending on the magnitude of the value.
|
||||
// For time_of_day or time_of_week functions the anomaly record
|
||||
// containing the timestamp of the anomaly should be supplied in
|
||||
// order to correctly format the day or week offset to the time of the anomaly.
|
||||
export function formatValue(value, mlFunction, fieldFormat, record) {
|
||||
// actual and typical values in anomaly record results will be arrays.
|
||||
// Unless the array is multi-valued (as it will be for multi-variate analyses such as lat_long),
|
||||
// simply return the formatted single value.
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 1) {
|
||||
return formatSingleValue(value[0], mlFunction, fieldFormat);
|
||||
return formatSingleValue(value[0], mlFunction, fieldFormat, record);
|
||||
} else {
|
||||
// Return with array style formatting.
|
||||
const values = value.map(val => formatSingleValue(val, mlFunction, fieldFormat));
|
||||
const values = value.map(val => formatSingleValue(val, mlFunction, fieldFormat, record));
|
||||
return `[${values}]`;
|
||||
}
|
||||
} else {
|
||||
return formatSingleValue(value, mlFunction, fieldFormat);
|
||||
return formatSingleValue(value, mlFunction, fieldFormat, record);
|
||||
}
|
||||
}
|
||||
|
||||
// Formats a single value according to the specified ML function.
|
||||
// If a Kibana fieldFormat is not supplied, will fall back to default
|
||||
// formatting depending on the magnitude of the value.
|
||||
function formatSingleValue(value, mlFunction, fieldFormat) {
|
||||
// For time_of_day or time_of_week functions the anomaly record
|
||||
// containing the timestamp of the anomaly should be supplied in
|
||||
// order to correctly format the day or week offset to the time of the anomaly.
|
||||
function formatSingleValue(value, mlFunction, fieldFormat, record) {
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If the analysis function is time_of_week/day, format as day/time.
|
||||
// For time_of_week / day, actual / typical is the UTC offset in seconds from the
|
||||
// start of the week / day, so need to manipulate to UTC moment of the start of the week / day
|
||||
// that the anomaly occurred using record timestamp if supplied, add on the offset, and finally
|
||||
// revert back to configured timezone for formatting.
|
||||
if (mlFunction === 'time_of_week') {
|
||||
const d = new Date();
|
||||
const d = ((record !== undefined && record.timestamp !== undefined) ? new Date(record.timestamp) : new Date());
|
||||
const i = parseInt(value);
|
||||
d.setTime(i * 1000);
|
||||
return moment(d).format('ddd hh:mm');
|
||||
const utcMoment = moment.utc(d).startOf('week').add(i, 's');
|
||||
return moment(utcMoment.valueOf()).format('ddd HH:mm');
|
||||
} else if (mlFunction === 'time_of_day') {
|
||||
const d = new Date();
|
||||
const d = ((record !== undefined && record.timestamp !== undefined) ? new Date(record.timestamp) : new Date());
|
||||
const i = parseInt(value);
|
||||
d.setTime(i * 1000);
|
||||
return moment(d).format('hh:mm');
|
||||
const utcMoment = moment.utc(d).startOf('day').add(i, 's');
|
||||
return moment(utcMoment.valueOf()).format('HH:mm');
|
||||
} else {
|
||||
if (fieldFormat !== undefined) {
|
||||
return fieldFormat.convert(value, 'text');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue