Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.x] [ML] Filter top influencer list based on swimlane selection (#18946) #18954

Merged
merged 1 commit into from
May 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
text-align: right;
line-height: 18px;
display: inline-block;

transition: none;
}
}

Expand Down
93 changes: 70 additions & 23 deletions x-pack/plugins/ml/public/explorer/explorer_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ module.controller('MlExplorerController', function (
let resizeTimeout = null;

const $mlExplorer = $('.ml-explorer');
const MAX_INFLUENCER_FIELD_NAMES = 10;
const MAX_INFLUENCER_FIELD_VALUES = 10;
const VIEW_BY_JOB_LABEL = 'job ID';

Expand Down Expand Up @@ -166,20 +165,6 @@ module.controller('MlExplorerController', function (
});
}

$scope.loadAnomaliesTable = function (jobIds, influencers, earliestMs, latestMs) {
mlResultsService.getRecordsForInfluencer(
jobIds, influencers, 0, earliestMs, latestMs, 500
)
.then((resp) => {
// Need to use $timeout to ensure the update happens after the child scope is updated with the new data.
$scope.$evalAsync(() => {
// Sort in descending time order before storing in scope.
$scope.anomalyRecords = _.chain(resp.records).sortBy(record => record[$scope.timeFieldName]).reverse().value();
console.log('Explorer anomalies table data set:', $scope.anomalyRecords);
});
});
};

$scope.setSelectedJobs = function (selectedIds) {
let previousSelected = 0;
if ($scope.selectedJobs !== null) {
Expand Down Expand Up @@ -367,7 +352,7 @@ module.controller('MlExplorerController', function (

$scope.cellData = cellData;
const args = [jobIds, influencers, timerange.earliestMs, timerange.latestMs];
$scope.loadAnomaliesTable(...args);
loadAnomalies(...args);
$scope.loadAnomaliesForCharts(...args);
}
};
Expand Down Expand Up @@ -424,7 +409,7 @@ module.controller('MlExplorerController', function (

$scope.loadAnomaliesForCharts = function (jobIds, influencers, earliestMs, latestMs) {
// Load the top anomalies (by record_score) which will be displayed in the charts.
// TODO - combine this with loadAnomaliesTable() if the table is being retained.
// TODO - combine this with loadAnomalies().
mlResultsService.getRecordsForInfluencer(
jobIds, influencers, 0, earliestMs, latestMs, 500
).then((resp) => {
Expand All @@ -439,6 +424,70 @@ module.controller('MlExplorerController', function (
});
};

function loadAnomalies(jobIds, influencers, earliestMs, latestMs) {
// Loads the anomalies for the table, plus the scores for
// the Top Influencers List for the influencers in the anomaly records.

if (influencers.length === 0) {
getTopInfluencers(jobIds, earliestMs, latestMs);
}

mlResultsService.getRecordsForInfluencer(
jobIds, influencers, 0, earliestMs, latestMs, 500
)
.then((resp) => {
if (influencers.length > 0) {
// Filter the Top Influencers list to show just the influencers from
// the records in the selected time range.
const recordInfluencersByName = {};
resp.records.forEach((record) => {
const influencersByName = record.influencers || [];
influencersByName.forEach((influencer) => {
const fieldName = influencer.influencer_field_name;
const fieldValues = influencer.influencer_field_values;
if (recordInfluencersByName[fieldName] === undefined) {
recordInfluencersByName[fieldName] = [];
}
recordInfluencersByName[fieldName].push(...fieldValues);
});
});

const uniqValuesByName = {};
Object.keys(recordInfluencersByName).forEach((fieldName) => {
const fieldValues = recordInfluencersByName[fieldName];
uniqValuesByName[fieldName] = _.uniq(fieldValues);
});

const filterInfluencers = [];
Object.keys(uniqValuesByName).forEach((fieldName) => {
// Find record influencers with the same field name as the clicked on cell(s).
const matchingFieldName = influencers.find((influencer) => {
return influencer.fieldName === fieldName;
});

if (matchingFieldName !== undefined) {
// Filter for the value(s) of the clicked on cell(s).
filterInfluencers.push(...influencers);
} else {
// For other field names, add values from all records.
uniqValuesByName[fieldName].forEach((fieldValue) => {
filterInfluencers.push({ fieldName, fieldValue });
});
}
});

getTopInfluencers(jobIds, earliestMs, latestMs, filterInfluencers);
}

// Use $evalAsync to ensure the update happens after the child scope is updated with the new data.
$scope.$evalAsync(() => {
// Sort in descending time order before storing in scope.
$scope.anomalyRecords = _.chain(resp.records).sortBy(record => record[$scope.timeFieldName]).reverse().value();
console.log('Explorer anomalies table data set:', $scope.anomalyRecords);
});
});
}

function loadViewBySwimlaneOptions() {
// Obtain the list of 'View by' fields per job.
$scope.swimlaneViewByFieldName = null;
Expand Down Expand Up @@ -600,14 +649,14 @@ module.controller('MlExplorerController', function (

}

function getTopInfluencers(selectedJobIds, earliestMs, latestMs) {
function getTopInfluencers(selectedJobIds, earliestMs, latestMs, influencers = []) {
if ($scope.noInfluencersConfigured !== true) {
mlResultsService.getTopInfluencers(
selectedJobIds,
earliestMs,
latestMs,
MAX_INFLUENCER_FIELD_NAMES,
MAX_INFLUENCER_FIELD_VALUES
MAX_INFLUENCER_FIELD_VALUES,
influencers
).then((resp) => {
// TODO - sort the influencers keys so that the partition field(s) are first.
$scope.influencers = resp.influencers;
Expand Down Expand Up @@ -689,7 +738,6 @@ module.controller('MlExplorerController', function (
selectedJobIds,
earliestMs,
latestMs,
MAX_INFLUENCER_FIELD_NAMES,
swimlaneLimit
).then((resp) => {
const topFieldValues = [];
Expand Down Expand Up @@ -725,8 +773,7 @@ module.controller('MlExplorerController', function (
const earliestMs = bounds.min.valueOf();
const latestMs = bounds.max.valueOf();
mlExplorerDashboardService.anomalyDataChange.changed($scope.anomalyChartRecords, earliestMs, latestMs);
getTopInfluencers(jobIds, earliestMs, latestMs);
$scope.loadAnomaliesTable(jobIds, [], earliestMs, latestMs);
loadAnomalies(jobIds, [], earliestMs, latestMs);
}

function calculateSwimlaneBucketInterval() {
Expand Down
30 changes: 28 additions & 2 deletions x-pack/plugins/ml/public/services/results_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,16 @@ function getScheduledEventsByBucket(

// Obtains the top influencers, by maximum influencer score, for the specified index, time range and job ID(s).
// Pass an empty array or ['*'] to search over all job IDs.
// An optional array of influencers may be supplied, with each object in the array having 'fieldName'
// and 'fieldValue' properties, to limit data to the supplied list of influencers.
// Returned response contains an influencers property, with a key for each of the influencer field names,
// whose value is an array of objects containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys.
function getTopInfluencers(jobIds, earliestMs, latestMs, maxFieldNames, maxFieldValues) {
function getTopInfluencers(
jobIds,
earliestMs,
latestMs,
maxFieldValues = 10,
influencers = []) {
return new Promise((resolve, reject) => {
const obj = { success: true, influencers: {} };

Expand Down Expand Up @@ -296,6 +303,25 @@ function getTopInfluencers(jobIds, earliestMs, latestMs, maxFieldNames, maxField
});
}

// Add a should query to filter for each of the specified influencers.
if (influencers.length > 0) {
boolCriteria.push({
bool: {
should: influencers.map((influencer) => {
return {
bool: {
must: [
{ term: { influencer_field_name: influencer.fieldName } },
{ term: { influencer_field_value: influencer.fieldValue } }
]
}
};
}),
minimum_should_match: 1,
}
});
}

ml.esSearch({
index: ML_RESULTS_INDEX_PATTERN,
size: 0,
Expand Down Expand Up @@ -335,7 +361,7 @@ function getTopInfluencers(jobIds, earliestMs, latestMs, maxFieldNames, maxField
influencerFieldValues: {
terms: {
field: 'influencer_field_value',
size: maxFieldValues !== undefined ? maxFieldValues : 10,
size: maxFieldValues,
order: {
maxAnomalyScore: 'desc'
}
Expand Down