Skip to content

Commit

Permalink
[ML] Anomaly Detection: Add single metric viewer embeddable for dashb…
Browse files Browse the repository at this point in the history
…oards (#175857)

## Summary

Related issue to [add ability to insert "Single Metric Viewer" into a
dashboard](#173555)

This PR adds the single metric viewer as an embeddable that can be added
to dashboards.

### NOTE FOR TESTING:

This PR relies on the SMV fix for 'metric' jobs
#176354
If that fix has not been merged, you will need to find
`getAnomalyRecordsSchema` definition and add `functionDescription:
schema.maybe(schema.nullable(schema.string())),` to it for local
testing.

### Screenshots of feature

<img width="698" alt="image"
src="https://github.com/elastic/kibana/assets/6446462/425e701a-3c9d-4a82-bf2e-1db5b3689165">

<img width="1193" alt="image"
src="https://github.com/elastic/kibana/assets/6446462/e941ec1c-14f6-4723-b80c-71124f617dc9">

<img width="1209" alt="image"
src="https://github.com/elastic/kibana/assets/6446462/dddd1dde-844c-47ae-ba94-61de5301746f">

<img width="1214" alt="image"
src="https://github.com/elastic/kibana/assets/6446462/39439b4f-d296-4f3d-bdc9-4922553af6fa">


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
alvarezmelissa87 and kibanamachine authored Feb 8, 2024
1 parent fa98e58 commit ee34012
Show file tree
Hide file tree
Showing 39 changed files with 3,341 additions and 136 deletions.
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

0 comments on commit ee34012

Please sign in to comment.