[ML] Fixes loading of Single Metric Viewer if partition field is text (#37975)
* [ML] Fixes loading of Single Metric Viewer if partition field is text * [ML] Fix failing test and edit translations for cardinality check * [ML] Edits to fields_service.js following review
This commit is contained in:
parent
d8b0368c5e
commit
4a3034de49
|
@ -128,6 +128,8 @@ function getChartDetails(job, detectorIndex, entityFields, earliestMs, latestMs)
|
|||
})
|
||||
.then((results) => {
|
||||
_.each(blankEntityFields, (field) => {
|
||||
// results will not contain keys for non-aggregatable fields,
|
||||
// so store as 0 to indicate over all field values.
|
||||
obj.results.entityData.entities.push({
|
||||
fieldName: field.fieldName,
|
||||
cardinality: _.get(results, field.fieldName, 0)
|
||||
|
|
|
@ -118,10 +118,11 @@
|
|||
<span
|
||||
ng-repeat="countData in chartDetails.entityData.entities"
|
||||
i18n-id="xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription"
|
||||
i18n-default-message="{openBrace}{cardinality} distinct {fieldName} {cardinality, plural, one {} other { values}}{closeBrace}"
|
||||
i18n-default-message="{openBrace}{cardinalityValue} distinct {fieldName} {cardinality, plural, one {} other { values}}{closeBrace}"
|
||||
i18n-values="{
|
||||
openBrace: $first ? '(' : '',
|
||||
closeBrace: $last ? ')' : ', ',
|
||||
cardinalityValue: countData.cardinality === 0 ? allValuesLabel : countData.cardinality,
|
||||
cardinality: countData.cardinality,
|
||||
fieldName: countData.fieldName
|
||||
}"
|
||||
|
|
|
@ -121,6 +121,13 @@ module.controller('MlTimeSeriesExplorerController', function (
|
|||
|
||||
$scope.focusAnnotationData = [];
|
||||
|
||||
// Used in the template to indicate the chart is being plotted across
|
||||
// all partition field values, where the cardinality of the field cannot be
|
||||
// obtained as it is not aggregatable e.g. 'all distinct kpi_indicator values'
|
||||
$scope.allValuesLabel = i18n.translate('xpack.ml.timeSeriesExplorer.allPartitionValuesLabel', {
|
||||
defaultMessage: 'all',
|
||||
});
|
||||
|
||||
// Pass the timezone to the server for use when aggregating anomalies (by day / hour) for the table.
|
||||
const tzConfig = config.get('dateFormat:tz');
|
||||
const dateFormatTz = (tzConfig !== 'Browser') ? tzConfig : moment.tz.guess();
|
||||
|
|
|
@ -13,6 +13,8 @@ export function fieldsServiceProvider(callWithRequest) {
|
|||
// Obtains the cardinality of one or more fields.
|
||||
// Returns an Object whose keys are the names of the fields,
|
||||
// with values equal to the cardinality of the field.
|
||||
// Any of the supplied fieldNames which are not aggregatable will
|
||||
// be omitted from the returned Object.
|
||||
function getCardinalityOfFields(
|
||||
index,
|
||||
fieldNames,
|
||||
|
@ -21,63 +23,95 @@ export function fieldsServiceProvider(callWithRequest) {
|
|||
earliestMs,
|
||||
latestMs) {
|
||||
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the time range and the datafeed config query.
|
||||
const mustCriteria = [
|
||||
{
|
||||
range: {
|
||||
[timeFieldName]: {
|
||||
gte: earliestMs,
|
||||
lte: latestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (query) {
|
||||
mustCriteria.push(query);
|
||||
}
|
||||
|
||||
const aggs = fieldNames.reduce((obj, field) => {
|
||||
obj[field] = { cardinality: { field } };
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const body = {
|
||||
query: {
|
||||
bool: {
|
||||
must: mustCriteria
|
||||
}
|
||||
},
|
||||
size: 0,
|
||||
_source: {
|
||||
excludes: []
|
||||
},
|
||||
aggs
|
||||
};
|
||||
|
||||
// First check that each of the supplied fieldNames are aggregatable,
|
||||
// then obtain the cardinality for each of the aggregatable fields.
|
||||
return new Promise((resolve, reject) => {
|
||||
callWithRequest('search', {
|
||||
callWithRequest('fieldCaps', {
|
||||
index,
|
||||
body
|
||||
fields: fieldNames
|
||||
})
|
||||
.then((resp) => {
|
||||
const aggregations = resp.aggregations;
|
||||
if (aggregations !== undefined) {
|
||||
const results = fieldNames.reduce((obj, field) => {
|
||||
obj[field] = (aggregations[field] || { value: 0 }).value;
|
||||
.then((fieldCapsResp) => {
|
||||
const aggregatableFields = [];
|
||||
|
||||
fieldNames.forEach((fieldName) => {
|
||||
const fieldInfo = fieldCapsResp.fields[fieldName];
|
||||
const typeKeys = Object.keys(fieldInfo);
|
||||
if (typeKeys.length > 0) {
|
||||
const fieldType = typeKeys[0];
|
||||
const isFieldAggregatable = fieldInfo[fieldType].aggregatable;
|
||||
if (isFieldAggregatable === true) {
|
||||
aggregatableFields.push(fieldName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (aggregatableFields.length > 0) {
|
||||
// Build the criteria to use in the bool filter part of the request.
|
||||
// Add criteria for the time range and the datafeed config query.
|
||||
const mustCriteria = [
|
||||
{
|
||||
range: {
|
||||
[timeFieldName]: {
|
||||
gte: earliestMs,
|
||||
lte: latestMs,
|
||||
format: 'epoch_millis'
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (query) {
|
||||
mustCriteria.push(query);
|
||||
}
|
||||
|
||||
const aggs = aggregatableFields.reduce((obj, field) => {
|
||||
obj[field] = { cardinality: { field } };
|
||||
return obj;
|
||||
}, {});
|
||||
resolve(results);
|
||||
|
||||
const body = {
|
||||
query: {
|
||||
bool: {
|
||||
must: mustCriteria
|
||||
}
|
||||
},
|
||||
size: 0,
|
||||
_source: {
|
||||
excludes: []
|
||||
},
|
||||
aggs
|
||||
};
|
||||
|
||||
callWithRequest('search', {
|
||||
index,
|
||||
body
|
||||
})
|
||||
.then((resp) => {
|
||||
const aggregations = resp.aggregations;
|
||||
if (aggregations !== undefined) {
|
||||
const results = aggregatableFields.reduce((obj, field) => {
|
||||
obj[field] = (aggregations[field] || { value: 0 }).value;
|
||||
return obj;
|
||||
}, {});
|
||||
resolve(results);
|
||||
} else {
|
||||
resolve({});
|
||||
}
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
} else {
|
||||
// None of the fields are aggregatable. Return empty Object.
|
||||
resolve({});
|
||||
}
|
||||
|
||||
})
|
||||
.catch((resp) => {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getTimeFieldRange(
|
||||
|
|
|
@ -29,6 +29,22 @@ describe('ML - validateModelMemoryLimit', () => {
|
|||
}
|
||||
};
|
||||
|
||||
// mock field caps response
|
||||
const fieldCapsResponse = {
|
||||
indices: [
|
||||
'cloudwatch'
|
||||
],
|
||||
fields: {
|
||||
instance: {
|
||||
keyword: {
|
||||
type: 'keyword',
|
||||
searchable: true,
|
||||
aggregatable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// mock cardinality search response
|
||||
const cardinalitySearchResponse = {
|
||||
took: 8,
|
||||
|
@ -52,9 +68,10 @@ describe('ML - validateModelMemoryLimit', () => {
|
|||
};
|
||||
|
||||
// mock callWithRequest
|
||||
// used in two places:
|
||||
// used in three places:
|
||||
// - to retrieve the info endpoint
|
||||
// - to search for cardinality of split field
|
||||
// - to retrieve field capabilities used in search for split field cardinality
|
||||
function callWithRequest(call) {
|
||||
if (typeof call === undefined) {
|
||||
return Promise.reject();
|
||||
|
@ -65,6 +82,8 @@ describe('ML - validateModelMemoryLimit', () => {
|
|||
response = mlInfoResponse;
|
||||
} else if(call === 'search') {
|
||||
response = cardinalitySearchResponse;
|
||||
} else if (call === 'fieldCaps') {
|
||||
response = fieldCapsResponse;
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
|
|
@ -7013,7 +7013,6 @@
|
|||
"xpack.ml.timeSeriesExplorer.anomaliesTitle": "異常",
|
||||
"xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText": "、初めのジョブを自動選択します",
|
||||
"xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage": "リクエストされた‘{invalidIdsCount, plural, one {ジョブ} other {ジョブ}} {invalidIds} をこのダッシュボードで表示できません",
|
||||
"xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription": "{openBrace}{cardinality} 特徴的な {fieldName} {cardinality, plural, one {} other { 値}}{closeBrace}",
|
||||
"xpack.ml.timeSeriesExplorer.createNewSingleMetricJobLinkText": "新規シングルメトリックジョブを作成",
|
||||
"xpack.ml.timeSeriesExplorer.dataNotChartableDescription": "選択された{entityCount, plural, one {エントリー} other {エントリー}}にはモデルプロットは収取されず、ソースデータはこのディテクター用にプロットできません",
|
||||
"xpack.ml.timeSeriesExplorer.deleteAnnotationModal.cancelButtonLabel": "キャンセル",
|
||||
|
|
|
@ -5747,7 +5747,6 @@
|
|||
"xpack.ml.timeSeriesExplorer.anomaliesTitle": "异常",
|
||||
"xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText": ",自动选择第一个作业",
|
||||
"xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage": "您无法在此仪表板中查看请求的 {invalidIdsCount, plural, one {作业} other {作业}} {invalidIds}",
|
||||
"xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription": "{openBrace}{cardinality} 个不同 {fieldName} {cardinality, plural, one {} other {值}}{closeBrace}",
|
||||
"xpack.ml.timeSeriesExplorer.createNewSingleMetricJobLinkText": "创建新的单指标作业",
|
||||
"xpack.ml.timeSeriesExplorer.deleteAnnotationModal.cancelButtonLabel": "取消",
|
||||
"xpack.ml.timeSeriesExplorer.deleteAnnotationModal.deleteAnnotationTitle": "删除此注释?",
|
||||
|
|
Loading…
Reference in a new issue