mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Enables display of contextual data for population charts using other metrics than count. (#24083)
- Enables support for showing contextual sampled data in population charts which use detector functions other than just count. - Use arrow functions where applicable in Anomaly Explorer Charts code.
This commit is contained in:
parent
6b22cd3a92
commit
82e97dd8e0
5 changed files with 23 additions and 20 deletions
|
@ -182,6 +182,7 @@ export class ExplorerChartDistribution extends React.Component {
|
|||
return d;
|
||||
}
|
||||
})
|
||||
// Don't use an arrow function since we need access to `this`.
|
||||
.each(function () {
|
||||
maxYAxisLabelWidth = Math.max(this.getBBox().width + yAxis.tickPadding(), maxYAxisLabelWidth);
|
||||
})
|
||||
|
@ -342,6 +343,7 @@ export class ExplorerChartDistribution extends React.Component {
|
|||
// Create any new dots that are needed i.e. if number of chart points has increased.
|
||||
dots.enter().append('circle')
|
||||
.attr('r', LINE_CHART_ANOMALY_RADIUS)
|
||||
// Don't use an arrow function since we need access to `this`.
|
||||
.on('mouseover', function (d) {
|
||||
showLineChartTooltip(d, this);
|
||||
})
|
||||
|
@ -349,9 +351,9 @@ export class ExplorerChartDistribution extends React.Component {
|
|||
|
||||
// Update all dots to new positions.
|
||||
const threshold = mlSelectSeverityService.state.get('threshold');
|
||||
dots.attr('cx', function (d) { return lineChartXScale(d.date); })
|
||||
.attr('cy', function (d) { return lineChartYScale(d[CHART_Y_ATTRIBUTE]); })
|
||||
.attr('class', function (d) {
|
||||
dots.attr('cx', d => lineChartXScale(d.date))
|
||||
.attr('cy', d => lineChartYScale(d[CHART_Y_ATTRIBUTE]))
|
||||
.attr('class', (d) => {
|
||||
let markerClass = 'metric-value';
|
||||
if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= threshold.val) {
|
||||
markerClass += ' anomaly-marker ';
|
||||
|
|
|
@ -138,6 +138,7 @@ export class ExplorerChartSingleMetric extends React.Component {
|
|||
return lineChartYScale.tickFormat()(d);
|
||||
}
|
||||
})
|
||||
// Don't use an arrow function since we need access to `this`.
|
||||
.each(function () {
|
||||
maxYAxisLabelWidth = Math.max(this.getBBox().width + yAxis.tickPadding(), maxYAxisLabelWidth);
|
||||
})
|
||||
|
@ -277,6 +278,7 @@ export class ExplorerChartSingleMetric extends React.Component {
|
|||
// Create any new dots that are needed i.e. if number of chart points has increased.
|
||||
dots.enter().append('circle')
|
||||
.attr('r', LINE_CHART_ANOMALY_RADIUS)
|
||||
// Don't use an arrow function since we need access to `this`.
|
||||
.on('mouseover', function (d) {
|
||||
showLineChartTooltip(d, this);
|
||||
})
|
||||
|
@ -284,9 +286,9 @@ export class ExplorerChartSingleMetric extends React.Component {
|
|||
|
||||
// Update all dots to new positions.
|
||||
const threshold = mlSelectSeverityService.state.get('threshold');
|
||||
dots.attr('cx', function (d) { return lineChartXScale(d.date); })
|
||||
.attr('cy', function (d) { return lineChartYScale(d.value); })
|
||||
.attr('class', function (d) {
|
||||
dots.attr('cx', d => lineChartXScale(d.date))
|
||||
.attr('cy', d => lineChartYScale(d.value))
|
||||
.attr('class', (d) => {
|
||||
let markerClass = 'metric-value';
|
||||
if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= threshold.val) {
|
||||
markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore)}`;
|
||||
|
@ -306,6 +308,7 @@ export class ExplorerChartSingleMetric extends React.Component {
|
|||
.attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross'))
|
||||
.attr('transform', d => `translate(${lineChartXScale(d.date)}, ${lineChartYScale(d.value)})`)
|
||||
.attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore)}`)
|
||||
// Don't use an arrow function since we need access to `this`.
|
||||
.on('mouseover', function (d) {
|
||||
showLineChartTooltip(d, this);
|
||||
})
|
||||
|
|
|
@ -76,7 +76,7 @@ export function explorerChartsContainerServiceFactory(
|
|||
// For now just take first 6 (or 8 if 4 charts per row).
|
||||
const maxSeriesToPlot = Math.max(chartsPerRow * 2, 6);
|
||||
const recordsToPlot = allSeriesRecords.slice(0, maxSeriesToPlot);
|
||||
const seriesConfigs = buildDataConfigs(recordsToPlot);
|
||||
const seriesConfigs = recordsToPlot.map(buildConfig);
|
||||
|
||||
// Calculate the time range of the charts, which is a function of the chart width and max job bucket span.
|
||||
data.tooManyBuckets = false;
|
||||
|
@ -504,11 +504,6 @@ export function explorerChartsContainerServiceFactory(
|
|||
return recordsForSeries;
|
||||
}
|
||||
|
||||
function buildDataConfigs(anomalyRecords) {
|
||||
// Build the chart configuration for each anomaly record.
|
||||
return anomalyRecords.map(buildConfig);
|
||||
}
|
||||
|
||||
function calculateChartRange(seriesConfigs, earliestMs, latestMs, chartWidth, recordsToPlot, timeFieldName) {
|
||||
let tooManyBuckets = false;
|
||||
// Calculate the time range for the charts.
|
||||
|
|
|
@ -1399,6 +1399,8 @@ function getEventRateData(
|
|||
// Returned response contains a results property, which is an object
|
||||
// of document counts against time (epoch millis).
|
||||
const SAMPLER_TOP_TERMS_SHARD_SIZE = 200;
|
||||
const ENTITY_AGGREGATION_SIZE = 10;
|
||||
const AGGREGATION_MIN_DOC_COUNT = 1;
|
||||
function getEventDistributionData(
|
||||
index,
|
||||
types,
|
||||
|
@ -1412,8 +1414,7 @@ function getEventDistributionData(
|
|||
latestMs,
|
||||
interval) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// only get this data for count (used by rare chart)
|
||||
if (metricFunction !== 'count' || splitField === undefined) {
|
||||
if (splitField === undefined) {
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
|
@ -1464,7 +1465,7 @@ function getEventDistributionData(
|
|||
date_histogram: {
|
||||
field: timeFieldName,
|
||||
interval: interval,
|
||||
min_doc_count: 0
|
||||
min_doc_count: AGGREGATION_MIN_DOC_COUNT
|
||||
},
|
||||
aggs: {
|
||||
sample: {
|
||||
|
@ -1475,7 +1476,8 @@ function getEventDistributionData(
|
|||
entities: {
|
||||
terms: {
|
||||
field: splitField.fieldName,
|
||||
size: 10
|
||||
size: ENTITY_AGGREGATION_SIZE,
|
||||
min_doc_count: AGGREGATION_MIN_DOC_COUNT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1491,7 +1493,7 @@ function getEventDistributionData(
|
|||
}
|
||||
|
||||
if (metricFieldName !== undefined && metricFieldName !== '') {
|
||||
body.aggs.byTime.aggs = {};
|
||||
body.aggs.byTime.aggs.sample.aggs.entities.aggs = {};
|
||||
|
||||
const metricAgg = {
|
||||
[metricFunction]: {
|
||||
|
@ -1502,7 +1504,7 @@ function getEventDistributionData(
|
|||
if (metricFunction === 'percentiles') {
|
||||
metricAgg[metricFunction].percents = [ML_MEDIAN_PERCENTS];
|
||||
}
|
||||
body.aggs.byTime.aggs.metric = metricAgg;
|
||||
body.aggs.byTime.aggs.sample.aggs.entities.aggs.metric = metricAgg;
|
||||
}
|
||||
|
||||
ml.esSearch({
|
||||
|
@ -1516,10 +1518,11 @@ function getEventDistributionData(
|
|||
const date = +dataForTime.key;
|
||||
const entities = _.get(dataForTime, ['sample', 'entities', 'buckets'], []);
|
||||
entities.forEach((entity) => {
|
||||
const value = (metricFunction === 'count') ? entity.doc_count : entity.metric.value;
|
||||
d.push({
|
||||
date,
|
||||
entity: entity.key,
|
||||
value: entity.doc_count
|
||||
value
|
||||
});
|
||||
});
|
||||
return d;
|
||||
|
|
|
@ -142,7 +142,7 @@ export function getChartType(config) {
|
|||
return CHART_TYPE.EVENT_DISTRIBUTION;
|
||||
} else if (
|
||||
POPULATION_DISTRIBUTION_ENABLED &&
|
||||
config.functionDescription === 'count' &&
|
||||
config.functionDescription !== 'rare' &&
|
||||
config.entityFields.some(f => f.fieldType === 'over')
|
||||
) {
|
||||
return CHART_TYPE.POPULATION_DISTRIBUTION;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue