[ML] Filter top influencer list based on swimlane selection (#18946)
* [ML] Filter top influencer list based on swimlane selection * [ML] Edits to Influencer List after review and remove animation
This commit is contained in:
parent
c8b8027866
commit
875a0eb3bc
|
@ -34,7 +34,7 @@
|
||||||
text-align: right;
|
text-align: right;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,6 @@ module.controller('MlExplorerController', function (
|
||||||
let resizeTimeout = null;
|
let resizeTimeout = null;
|
||||||
|
|
||||||
const $mlExplorer = $('.ml-explorer');
|
const $mlExplorer = $('.ml-explorer');
|
||||||
const MAX_INFLUENCER_FIELD_NAMES = 10;
|
|
||||||
const MAX_INFLUENCER_FIELD_VALUES = 10;
|
const MAX_INFLUENCER_FIELD_VALUES = 10;
|
||||||
const VIEW_BY_JOB_LABEL = 'job ID';
|
const VIEW_BY_JOB_LABEL = 'job ID';
|
||||||
|
|
||||||
|
@ -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) {
|
$scope.setSelectedJobs = function (selectedIds) {
|
||||||
let previousSelected = 0;
|
let previousSelected = 0;
|
||||||
if ($scope.selectedJobs !== null) {
|
if ($scope.selectedJobs !== null) {
|
||||||
|
@ -367,7 +352,7 @@ module.controller('MlExplorerController', function (
|
||||||
|
|
||||||
$scope.cellData = cellData;
|
$scope.cellData = cellData;
|
||||||
const args = [jobIds, influencers, timerange.earliestMs, timerange.latestMs];
|
const args = [jobIds, influencers, timerange.earliestMs, timerange.latestMs];
|
||||||
$scope.loadAnomaliesTable(...args);
|
loadAnomalies(...args);
|
||||||
$scope.loadAnomaliesForCharts(...args);
|
$scope.loadAnomaliesForCharts(...args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -424,7 +409,7 @@ module.controller('MlExplorerController', function (
|
||||||
|
|
||||||
$scope.loadAnomaliesForCharts = function (jobIds, influencers, earliestMs, latestMs) {
|
$scope.loadAnomaliesForCharts = function (jobIds, influencers, earliestMs, latestMs) {
|
||||||
// Load the top anomalies (by record_score) which will be displayed in the charts.
|
// 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(
|
mlResultsService.getRecordsForInfluencer(
|
||||||
jobIds, influencers, 0, earliestMs, latestMs, 500
|
jobIds, influencers, 0, earliestMs, latestMs, 500
|
||||||
).then((resp) => {
|
).then((resp) => {
|
||||||
|
@ -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() {
|
function loadViewBySwimlaneOptions() {
|
||||||
// Obtain the list of 'View by' fields per job.
|
// Obtain the list of 'View by' fields per job.
|
||||||
$scope.swimlaneViewByFieldName = null;
|
$scope.swimlaneViewByFieldName = null;
|
||||||
|
@ -600,14 +649,14 @@ module.controller('MlExplorerController', function (
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTopInfluencers(selectedJobIds, earliestMs, latestMs) {
|
function getTopInfluencers(selectedJobIds, earliestMs, latestMs, influencers = []) {
|
||||||
if ($scope.noInfluencersConfigured !== true) {
|
if ($scope.noInfluencersConfigured !== true) {
|
||||||
mlResultsService.getTopInfluencers(
|
mlResultsService.getTopInfluencers(
|
||||||
selectedJobIds,
|
selectedJobIds,
|
||||||
earliestMs,
|
earliestMs,
|
||||||
latestMs,
|
latestMs,
|
||||||
MAX_INFLUENCER_FIELD_NAMES,
|
MAX_INFLUENCER_FIELD_VALUES,
|
||||||
MAX_INFLUENCER_FIELD_VALUES
|
influencers
|
||||||
).then((resp) => {
|
).then((resp) => {
|
||||||
// TODO - sort the influencers keys so that the partition field(s) are first.
|
// TODO - sort the influencers keys so that the partition field(s) are first.
|
||||||
$scope.influencers = resp.influencers;
|
$scope.influencers = resp.influencers;
|
||||||
|
@ -689,7 +738,6 @@ module.controller('MlExplorerController', function (
|
||||||
selectedJobIds,
|
selectedJobIds,
|
||||||
earliestMs,
|
earliestMs,
|
||||||
latestMs,
|
latestMs,
|
||||||
MAX_INFLUENCER_FIELD_NAMES,
|
|
||||||
swimlaneLimit
|
swimlaneLimit
|
||||||
).then((resp) => {
|
).then((resp) => {
|
||||||
const topFieldValues = [];
|
const topFieldValues = [];
|
||||||
|
@ -725,8 +773,7 @@ module.controller('MlExplorerController', function (
|
||||||
const earliestMs = bounds.min.valueOf();
|
const earliestMs = bounds.min.valueOf();
|
||||||
const latestMs = bounds.max.valueOf();
|
const latestMs = bounds.max.valueOf();
|
||||||
mlExplorerDashboardService.anomalyDataChange.changed($scope.anomalyChartRecords, earliestMs, latestMs);
|
mlExplorerDashboardService.anomalyDataChange.changed($scope.anomalyChartRecords, earliestMs, latestMs);
|
||||||
getTopInfluencers(jobIds, earliestMs, latestMs);
|
loadAnomalies(jobIds, [], earliestMs, latestMs);
|
||||||
$scope.loadAnomaliesTable(jobIds, [], earliestMs, latestMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateSwimlaneBucketInterval() {
|
function calculateSwimlaneBucketInterval() {
|
||||||
|
|
|
@ -259,9 +259,16 @@ function getScheduledEventsByBucket(
|
||||||
|
|
||||||
// Obtains the top influencers, by maximum influencer score, for the specified index, time range and job ID(s).
|
// 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.
|
// 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,
|
// 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.
|
// 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const obj = { success: true, influencers: {} };
|
const obj = { success: true, influencers: {} };
|
||||||
|
|
||||||
|
@ -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({
|
ml.esSearch({
|
||||||
index: ML_RESULTS_INDEX_PATTERN,
|
index: ML_RESULTS_INDEX_PATTERN,
|
||||||
size: 0,
|
size: 0,
|
||||||
|
@ -335,7 +361,7 @@ function getTopInfluencers(jobIds, earliestMs, latestMs, maxFieldNames, maxField
|
||||||
influencerFieldValues: {
|
influencerFieldValues: {
|
||||||
terms: {
|
terms: {
|
||||||
field: 'influencer_field_value',
|
field: 'influencer_field_value',
|
||||||
size: maxFieldValues !== undefined ? maxFieldValues : 10,
|
size: maxFieldValues,
|
||||||
order: {
|
order: {
|
||||||
maxAnomalyScore: 'desc'
|
maxAnomalyScore: 'desc'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue