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

[ML] Anomaly Detection: Adds single metric viewer embeddable for dashboards #175857

Merged
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c144d04
wip: create single metric viewer action in dashboard
alvarezmelissa87 Dec 13, 2023
a272e21
wip: add smv as embeddable that can be viewed in dashboards
alvarezmelissa87 Dec 21, 2023
4cae098
fix multiple charts issue
alvarezmelissa87 Jan 30, 2024
978f019
fix slider listener for multiple smv charts
alvarezmelissa87 Jan 31, 2024
8333406
remove unnecessary comments
alvarezmelissa87 Jan 31, 2024
efce040
add unique id to checkbox
alvarezmelissa87 Feb 1, 2024
e2e9388
fix entity selection controls
alvarezmelissa87 Feb 2, 2024
d931543
ensure panel settings are applied
alvarezmelissa87 Feb 3, 2024
c5304be
remove annototaion creation when embeddable
alvarezmelissa87 Feb 5, 2024
85d0042
remove annotations help text for embeddable
alvarezmelissa87 Feb 5, 2024
a744275
ensure type is correct
alvarezmelissa87 Feb 5, 2024
c5181f1
remove unnecessary services from context
alvarezmelissa87 Feb 5, 2024
ba542f1
Merge branch 'main' into ml-ad-single-metric-embeddable
kibanamachine Feb 5, 2024
9078703
fix translations
alvarezmelissa87 Feb 5, 2024
f682daa
update naming
alvarezmelissa87 Feb 6, 2024
4b514c3
fix metric jobs in SMV in dashboard and ML
alvarezmelissa87 Feb 7, 2024
659f1ca
update naming from provider to factory for consistency
alvarezmelissa87 Feb 7, 2024
8dedbde
Merge branch 'main' into ml-ad-single-metric-embeddable
kibanamachine Feb 7, 2024
39988a0
Merge branch 'main' into ml-ad-single-metric-embeddable
kibanamachine Feb 8, 2024
1bd6b8b
add comment to consolidate all duplicated services
alvarezmelissa87 Feb 8, 2024
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
4 changes: 4 additions & 0 deletions x-pack/packages/ml/anomaly_utils/anomaly_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export interface MlEntityField {
* Optional entity field operation
*/
operation?: MlEntityFieldOperation;
/**
* Optional cardinality of field
*/
cardinality?: number;
}

// List of function descriptions for which actual values from record level results should be displayed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@
import { mlFunctionToESAggregation } from '../../../common/util/job_utils';
import { getDataViewById, getDataViewIdFromName } from '../util/index_utils';
import { mlJobService } from './job_service';
import type { MlIndexUtils } from '../util/index_service';
import type { MlApiServices } from './ml_api_service';

type FormatsByJobId = Record<string, any>;
type IndexPatternIdsByJob = Record<string, any>;

// Service for accessing FieldFormat objects configured for a Kibana data view
// for use in formatting the actual and typical values from anomalies.
class FieldFormatService {
export class FieldFormatService {
indexPatternIdsByJob: IndexPatternIdsByJob = {};
formatsByJob: FormatsByJobId = {};

constructor(private mlApiServices?: MlApiServices, private mlIndexUtils?: MlIndexUtils) {}

// Populate the service with the FieldFormats for the list of jobs with the
// specified IDs. List of Kibana data views is passed, with a title
// attribute set in each pattern which will be compared to the indices
Expand All @@ -32,10 +36,17 @@ class FieldFormatService {
(
await Promise.all(
jobIds.map(async (jobId) => {
const jobObj = mlJobService.getJob(jobId);
const getDataViewId = this.mlIndexUtils?.getDataViewIdFromName ?? getDataViewIdFromName;
let jobObj;
if (this.mlApiServices) {
const { jobs } = await this.mlApiServices.getJobs({ jobId });
jobObj = jobs[0];
} else {
jobObj = mlJobService.getJob(jobId);
}
return {
jobId,
dataViewId: await getDataViewIdFromName(jobObj.datafeed_config.indices.join(',')),
dataViewId: await getDataViewId(jobObj.datafeed_config!.indices.join(',')),
};
})
)
Expand Down Expand Up @@ -68,41 +79,40 @@ class FieldFormatService {
}
}

getFormatsForJob(jobId: string): Promise<any[]> {
return new Promise((resolve, reject) => {
const jobObj = mlJobService.getJob(jobId);
const detectors = jobObj.analysis_config.detectors || [];
const formatsByDetector: any[] = [];
async getFormatsForJob(jobId: string): Promise<any[]> {
let jobObj;
const getDataView = this.mlIndexUtils?.getDataViewById ?? getDataViewById;
if (this.mlApiServices) {
const { jobs } = await this.mlApiServices.getJobs({ jobId });
jobObj = jobs[0];
} else {
jobObj = mlJobService.getJob(jobId);
}
const detectors = jobObj.analysis_config.detectors || [];
const formatsByDetector: any[] = [];

const dataViewId = this.indexPatternIdsByJob[jobId];
if (dataViewId !== undefined) {
// Load the full data view configuration to obtain the formats of each field.
getDataViewById(dataViewId)
.then((dataView) => {
// Store the FieldFormat for each job by detector_index.
const fieldList = dataView.fields;
detectors.forEach((dtr) => {
const esAgg = mlFunctionToESAggregation(dtr.function);
// distinct_count detectors should fall back to the default
// formatter as the values are just counts.
if (dtr.field_name !== undefined && esAgg !== 'cardinality') {
const field = fieldList.getByName(dtr.field_name);
if (field !== undefined) {
formatsByDetector[dtr.detector_index!] = dataView.getFormatterForField(field);
}
}
});
const dataViewId = this.indexPatternIdsByJob[jobId];
if (dataViewId !== undefined) {
// Load the full data view configuration to obtain the formats of each field.
const dataView = await getDataView(dataViewId);
// Store the FieldFormat for each job by detector_index.
const fieldList = dataView.fields;
detectors.forEach((dtr) => {
const esAgg = mlFunctionToESAggregation(dtr.function);
// distinct_count detectors should fall back to the default
// formatter as the values are just counts.
if (dtr.field_name !== undefined && esAgg !== 'cardinality') {
const field = fieldList.getByName(dtr.field_name);
if (field !== undefined) {
formatsByDetector[dtr.detector_index!] = dataView.getFormatterForField(field);
}
}
});
}

resolve(formatsByDetector);
})
.catch((err) => {
reject(err);
});
} else {
resolve(formatsByDetector);
}
});
return formatsByDetector;
}
}

export const mlFieldFormatService = new FieldFormatService();
export type MlFieldFormatService = typeof mlFieldFormatService;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { type MlFieldFormatService, FieldFormatService } from './field_format_service';
import type { MlIndexUtils } from '../util/index_service';
import type { MlApiServices } from './ml_api_service';

export function fieldFormatServiceFactory(
mlApiServices: MlApiServices,
mlIndexUtils: MlIndexUtils
): MlFieldFormatService {
return new FieldFormatService(mlApiServices, mlIndexUtils);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export const mlForecastService: {

getForecastDateRange: (job: Job, forecastId: string) => Promise<ForecastDateRange>;
};

export type MlForecastService = typeof mlForecastService;
Loading