[ML] Package @kbn/ml-data-grid (#155530)

Refactors outdated approach of sharing the custom data grid component
from the `ml` plugin to the `transform` plugin to use packages instead.

Creates the following packages:

- `@kbn/ml-data-grid`
- `@kbn/ml-date-utils`
- `@kbn/ml-runtime-field-utils`
----

- Imports have been refactored so the `transform` plugin no longer
imports code from packages `@kbn/ml-anomaly-utils` and
`@kbn/ml-data-frame-analytics-utils`.
- Removed again auto-generated `@type` related JSDoc comments. Within
the code those annotations are quite redundant, would be cumbersome to
maintain and they are not necessary to satisfy the "missing comments"
check.
- The `renderCellPopover` callback has been refactored out of the
`DataGrid` component and can now be passed in as an optional prop. It is
only used for Data Frame Analytics and this way we can avoid some
dependency of the transform plugin on DFA related code.
- Some more code has been moved to `@kbn/ml-anomaly-utils` to make
available via packages what's needed for the data grid.
This commit is contained in:
Walter Rafelsberger 2023-06-06 11:20:37 +02:00 committed by GitHub
parent 20d4fc416a
commit 9492c932bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
269 changed files with 2146 additions and 1778 deletions

3
.github/CODEOWNERS vendored
View file

@ -464,7 +464,9 @@ x-pack/packages/maps/vector_tile_utils @elastic/kibana-gis
x-pack/packages/ml/agg_utils @elastic/ml-ui
x-pack/packages/ml/anomaly_utils @elastic/ml-ui
x-pack/packages/ml/data_frame_analytics_utils @elastic/ml-ui
x-pack/packages/ml/data_grid @elastic/ml-ui
x-pack/packages/ml/date_picker @elastic/ml-ui
x-pack/packages/ml/date_utils @elastic/ml-ui
x-pack/packages/ml/error_utils @elastic/ml-ui
x-pack/packages/ml/is_defined @elastic/ml-ui
x-pack/packages/ml/is_populated_object @elastic/ml-ui
@ -476,6 +478,7 @@ x-pack/plugins/ml @elastic/ml-ui
x-pack/packages/ml/query_utils @elastic/ml-ui
x-pack/packages/ml/random_sampler_utils @elastic/ml-ui
x-pack/packages/ml/route_utils @elastic/ml-ui
x-pack/packages/ml/runtime_field_utils @elastic/ml-ui
x-pack/packages/ml/string_hash @elastic/ml-ui
x-pack/packages/ml/trained_models_utils @elastic/ml-ui
x-pack/packages/ml/url_state @elastic/ml-ui

View file

@ -480,7 +480,9 @@
"@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils",
"@kbn/ml-anomaly-utils": "link:x-pack/packages/ml/anomaly_utils",
"@kbn/ml-data-frame-analytics-utils": "link:x-pack/packages/ml/data_frame_analytics_utils",
"@kbn/ml-data-grid": "link:x-pack/packages/ml/data_grid",
"@kbn/ml-date-picker": "link:x-pack/packages/ml/date_picker",
"@kbn/ml-date-utils": "link:x-pack/packages/ml/date_utils",
"@kbn/ml-error-utils": "link:x-pack/packages/ml/error_utils",
"@kbn/ml-is-defined": "link:x-pack/packages/ml/is_defined",
"@kbn/ml-is-populated-object": "link:x-pack/packages/ml/is_populated_object",
@ -492,6 +494,7 @@
"@kbn/ml-query-utils": "link:x-pack/packages/ml/query_utils",
"@kbn/ml-random-sampler-utils": "link:x-pack/packages/ml/random_sampler_utils",
"@kbn/ml-route-utils": "link:x-pack/packages/ml/route_utils",
"@kbn/ml-runtime-field-utils": "link:x-pack/packages/ml/runtime_field_utils",
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
"@kbn/ml-trained-models-utils": "link:x-pack/packages/ml/trained_models_utils",
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",

View file

@ -922,8 +922,12 @@
"@kbn/ml-anomaly-utils/*": ["x-pack/packages/ml/anomaly_utils/*"],
"@kbn/ml-data-frame-analytics-utils": ["x-pack/packages/ml/data_frame_analytics_utils"],
"@kbn/ml-data-frame-analytics-utils/*": ["x-pack/packages/ml/data_frame_analytics_utils/*"],
"@kbn/ml-data-grid": ["x-pack/packages/ml/data_grid"],
"@kbn/ml-data-grid/*": ["x-pack/packages/ml/data_grid/*"],
"@kbn/ml-date-picker": ["x-pack/packages/ml/date_picker"],
"@kbn/ml-date-picker/*": ["x-pack/packages/ml/date_picker/*"],
"@kbn/ml-date-utils": ["x-pack/packages/ml/date_utils"],
"@kbn/ml-date-utils/*": ["x-pack/packages/ml/date_utils/*"],
"@kbn/ml-error-utils": ["x-pack/packages/ml/error_utils"],
"@kbn/ml-error-utils/*": ["x-pack/packages/ml/error_utils/*"],
"@kbn/ml-is-defined": ["x-pack/packages/ml/is_defined"],
@ -946,6 +950,8 @@
"@kbn/ml-random-sampler-utils/*": ["x-pack/packages/ml/random_sampler_utils/*"],
"@kbn/ml-route-utils": ["x-pack/packages/ml/route_utils"],
"@kbn/ml-route-utils/*": ["x-pack/packages/ml/route_utils/*"],
"@kbn/ml-runtime-field-utils": ["x-pack/packages/ml/runtime_field_utils"],
"@kbn/ml-runtime-field-utils/*": ["x-pack/packages/ml/runtime_field_utils/*"],
"@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"],
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
"@kbn/ml-trained-models-utils": ["x-pack/packages/ml/trained_models_utils"],

View file

@ -49,6 +49,7 @@
"xpack.maps": ["plugins/maps"],
"xpack.ml": [
"packages/ml/anomaly_utils",
"packages/ml/data_grid",
"packages/ml/date_picker",
"packages/ml/trained_models_utils",
"plugins/ml"

View file

@ -8,6 +8,7 @@
export { buildSamplerAggregation } from './src/build_sampler_aggregation';
export { fetchAggIntervals } from './src/fetch_agg_intervals';
export { fetchHistogramsForFields } from './src/fetch_histograms_for_fields';
export { DEFAULT_SAMPLER_SHARD_SIZE } from './src/field_histograms';
export { getSamplerAggregationsResponsePath } from './src/get_sampler_aggregations_response_path';
export { numberValidator } from './src/validate_number';
@ -16,6 +17,7 @@ export type {
NumericChartData,
NumericHistogramField,
} from './src/fetch_histograms_for_fields';
export { isMultiBucketAggregate } from './src/is_multi_bucket_aggregate';
export { isSignificantTerm } from './src/type_guards';
export type {
AggCardinality,

View file

@ -5,5 +5,7 @@
* 2.0.
*/
// Default sampler shard size used for field histograms
/**
* Default sampler shard size used for field histograms
*/
export const DEFAULT_SAMPLER_SHARD_SIZE = 5000;

View file

@ -0,0 +1,22 @@
/*
* 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
/**
* Type guard to validate multi bucket aggregate format.
*
* @template TBucket
* @param {unknown} arg - The item to be checked.
* @returns {arg is estypes.AggregationsMultiBucketAggregateBase<TBucket>}
*/
export const isMultiBucketAggregate = <TBucket = unknown>(
arg: unknown
): arg is estypes.AggregationsMultiBucketAggregateBase<TBucket> => {
return isPopulatedObject(arg, ['buckets']);
};

View file

@ -5,8 +5,11 @@
* 2.0.
*/
import { Aggregation, METRIC_AGG_TYPE } from '../types/fields';
import { type Aggregation, METRIC_AGG_TYPE } from './fields';
/**
* Enum for ML job aggregations.
*/
export enum ML_JOB_AGGREGATION {
// count
COUNT = 'count',
@ -58,6 +61,9 @@ export enum ML_JOB_AGGREGATION {
LAT_LONG = 'lat_long',
}
/**
* Custom enum for sparse data aggregations.
*/
export const SPARSE_DATA_AGGREGATIONS = [
ML_JOB_AGGREGATION.NON_ZERO_COUNT,
ML_JOB_AGGREGATION.HIGH_NON_ZERO_COUNT,
@ -65,8 +71,16 @@ export const SPARSE_DATA_AGGREGATIONS = [
ML_JOB_AGGREGATION.NON_NULL_SUM,
ML_JOB_AGGREGATION.HIGH_NON_NULL_SUM,
ML_JOB_AGGREGATION.LOW_NON_NULL_SUM,
];
] as const;
/**
* Type definition of SPARSE_DATA_AGGREGATIONS values.
*/
export type SparseDataAggregation = typeof SPARSE_DATA_AGGREGATIONS[number];
/**
* Enum for Kibana aggregations.
*/
export enum KIBANA_AGGREGATION {
COUNT = 'count',
AVG = 'avg',
@ -77,6 +91,9 @@ export enum KIBANA_AGGREGATION {
CARDINALITY = 'cardinality',
}
/**
* Enum for ES aggregatins.
*/
export enum ES_AGGREGATION {
COUNT = 'count',
AVG = 'avg',
@ -87,8 +104,11 @@ export enum ES_AGGREGATION {
CARDINALITY = 'cardinality',
}
// aggregation object missing id, title and fields and has null for kibana and dsl aggregation names.
// this is used as the basis for the ML only aggregations
/**
* Aggregation object missing id, title and fields and has null for kibana and dsl aggregation names.
* This is used as the basis for the ML only aggregations.
* @returns {Omit<Aggregation, 'id' | 'title' | 'fields'>}
*/
function getBasicMlOnlyAggregation(): Omit<Aggregation, 'id' | 'title' | 'fields'> {
return {
kibanaName: null,
@ -101,9 +121,11 @@ function getBasicMlOnlyAggregation(): Omit<Aggregation, 'id' | 'title' | 'fields
};
}
// list of aggregations only support by ML and which don't have an equivalent ES aggregation
// note, not all aggs have a field list. Some aggs cannot be used with a field.
export const mlOnlyAggregations: Aggregation[] = [
/**
* List of aggregations only supported by ML and which don't have an equivalent ES aggregation.
* Note, not all aggs have a field list. Some aggs cannot be used with a field.
*/
export const mlJobAggregationsWithoutEsEquivalent: Aggregation[] = [
{
id: ML_JOB_AGGREGATION.NON_ZERO_COUNT,
title: 'Non zero count',
@ -219,7 +241,10 @@ export const mlOnlyAggregations: Aggregation[] = [
},
];
export const aggregations: Aggregation[] = [
/**
* ML job aggregation definitions.
*/
export const mlJobAggregations: Aggregation[] = [
{
id: ML_JOB_AGGREGATION.COUNT,
title: 'Count',

View file

@ -44,20 +44,14 @@ export enum ML_ANOMALY_SEVERITY {
/**
* Interface for severity types to be used in ML_ANOMALY_SEVERITY_TYPES.
*
* @export
* @interface MlSeverityType
* @typedef {MlSeverityType}
*/
export interface MlSeverityType {
/**
* One of ML_ANOMALY_SEVERITY
* @type {ML_ANOMALY_SEVERITY}
*/
id: ML_ANOMALY_SEVERITY;
/**
* Translated ML_ANOMALY_SEVERITY
* @type {string}
*/
label: string;
}

View file

@ -18,8 +18,6 @@ import { ML_DETECTOR_RULE_CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from './detector_
/**
* Enum of entity field types
* @export
* @enum {number}
*/
export enum ML_ENTITY_FIELD_TYPE {
BY = 'by',
@ -29,7 +27,6 @@ export enum ML_ENTITY_FIELD_TYPE {
/**
* Custom enum of entity field operations
* @type {{ readonly ADD: "+"; readonly REMOVE: "-"; }}
*/
export const ML_ENTITY_FIELD_OPERATIONS = {
ADD: '+',
@ -38,37 +35,28 @@ export const ML_ENTITY_FIELD_OPERATIONS = {
/**
* Union type of entity field operations
* @export
* @typedef {MlEntityFieldOperation}
*/
export type MlEntityFieldOperation =
typeof ML_ENTITY_FIELD_OPERATIONS[keyof typeof ML_ENTITY_FIELD_OPERATIONS];
/**
* Interface of an entity field
* @export
* @interface MlEntityField
* @typedef {MlEntityField}
*/
export interface MlEntityField {
/**
* The field name
* @type {string}
*/
fieldName: string;
/**
* The field value
* @type {(string | number | undefined)}
*/
fieldValue: string | number | undefined;
/**
* Optional field type
* @type {?ML_ENTITY_FIELD_TYPE}
*/
fieldType?: ML_ENTITY_FIELD_TYPE;
/**
* Optional entity field operation
* @type {?MlEntityFieldOperation}
*/
operation?: MlEntityFieldOperation;
}

View file

@ -32,7 +32,6 @@ export const ML_SEVERITY_COLOR_RAMP = [
/**
* Custom enum for anomaly result type
* @type {{ readonly BUCKET: "bucket"; readonly RECORD: "record"; readonly INFLUENCER: "influencer"; }}
*/
export const ML_ANOMALY_RESULT_TYPE = {
BUCKET: 'bucket',
@ -42,18 +41,15 @@ export const ML_ANOMALY_RESULT_TYPE = {
/**
* Array of partition fields.
* @type {readonly ["partition_field", "over_field", "by_field"]}
*/
export const ML_PARTITION_FIELDS = ['partition_field', 'over_field', 'by_field'] as const;
/**
* Machine learning job id attribute name.
* @type {"job_id"}
*/
export const ML_JOB_ID = 'job_id';
/**
* Machine learning partition field value attribute name.
* @type {"partition_field_value"}
*/
export const ML_PARTITION_FIELD_VALUE = 'partition_field_value';

View file

@ -10,81 +10,53 @@ import type { MlAnomalyRecordDoc } from './types';
/**
* Base Interface for basic custom URL.
*
* @export
* @interface BaseUrlConfig
* @typedef {BaseUrlConfig}
*/
interface BaseUrlConfig {
/**
* The url name of the configuration.
* @type {string}
*/
url_name: string;
/**
* The url value of the configuration.
* @type {string}
*/
url_value: string;
}
/**
* Extended interface for custom URLs including an optional time range.
*
* @export
* @interface MlKibanaUrlConfig
* @typedef {MlKibanaUrlConfig}
* @extends {BaseUrlConfig}
*/
export interface MlKibanaUrlConfig extends BaseUrlConfig {
/**
* The optional time range for the custom URL configuration
* @type {?string}
*/
time_range?: string;
}
/**
* Extended interface for custom URLs including a time range.
*
* @export
* @interface MlKibanaUrlConfigWithTimeRange
* @typedef {MlKibanaUrlConfigWithTimeRange}
* @extends {BaseUrlConfig}
*/
export interface MlKibanaUrlConfigWithTimeRange extends BaseUrlConfig {
/**
* The time range for the custom URL configuration
* @type {string}
*/
time_range: string;
}
/**
* Union type of different custom URL configurations
*
* @export
* @typedef {MlUrlConfig}
*/
export type MlUrlConfig = BaseUrlConfig | MlKibanaUrlConfig;
/**
* Extended interface of MlAnomalyRecordDoc to include time range information.
*
* @export
* @interface MlCustomUrlAnomalyRecordDoc
* @typedef {MlCustomUrlAnomalyRecordDoc}
* @extends {MlAnomalyRecordDoc}
*/
export interface MlCustomUrlAnomalyRecordDoc extends MlAnomalyRecordDoc {
/**
* The `earliest` timestamp.
* @type {string}
*/
earliest: string;
/**
* The `latest` timestamp.
* @type {string}
*/
latest: string;
}
@ -92,7 +64,6 @@ export interface MlCustomUrlAnomalyRecordDoc extends MlAnomalyRecordDoc {
/**
* Type guard to idenfity MlKibanaUrlConfigWithTimeRange.
*
* @export
* @param {unknown} arg The unknown type to be evaluated
* @returns {arg is MlKibanaUrlConfigWithTimeRange} whether arg is of type MlKibanaUrlConfigWithTimeRange
*/

View file

@ -11,8 +11,6 @@
/**
* Enum ML_DETECTOR_RULE_ACTION
* @export
* @enum {number}
*/
export enum ML_DETECTOR_RULE_ACTION {
SKIP_MODEL_UPDATE = 'skip_model_update',
@ -21,8 +19,6 @@ export enum ML_DETECTOR_RULE_ACTION {
/**
* Enum ML_DETECTOR_RULE_FILTER_TYPE
* @export
* @enum {number}
*/
export enum ML_DETECTOR_RULE_FILTER_TYPE {
EXCLUDE = 'exclude',
@ -31,8 +27,6 @@ export enum ML_DETECTOR_RULE_FILTER_TYPE {
/**
* Enum ML_DETECTOR_RULE_APPLIES_TO
* @export
* @enum {number}
*/
export enum ML_DETECTOR_RULE_APPLIES_TO {
ACTUAL = 'actual',
@ -42,8 +36,6 @@ export enum ML_DETECTOR_RULE_APPLIES_TO {
/**
* Enum ML_DETECTOR_RULE_OPERATOR
* @export
* @enum {number}
*/
export enum ML_DETECTOR_RULE_OPERATOR {
LESS_THAN = 'lt',

View file

@ -0,0 +1,13 @@
/*
* 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
/**
* Alias for QueryDslQueryContainer
*/
export type InfluencersFilterQuery = estypes.QueryDslQueryContainer;

View file

@ -5,6 +5,9 @@
* 2.0.
*/
/**
* Custom enum for ML job field types
*/
export const ML_JOB_FIELD_TYPES = {
BOOLEAN: 'boolean',
DATE: 'date',
@ -17,6 +20,14 @@ export const ML_JOB_FIELD_TYPES = {
UNKNOWN: 'unknown',
} as const;
/**
* Union type for ML_JOB_FIELD_TYPES
*/
export type MlJobFieldType = typeof ML_JOB_FIELD_TYPES[keyof typeof ML_JOB_FIELD_TYPES];
/**
* MLCATEGORY
*/
export const MLCATEGORY = 'mlcategory';
/**
@ -30,5 +41,7 @@ export const DOC_COUNT = 'doc_count';
*/
export const _DOC_COUNT = '_doc_count';
// List of system fields we don't want to display.
/**
* List of system fields we don't want to display.
*/
export const OMIT_FIELDS: string[] = ['_source', '_type', '_index', '_id', '_version', '_score'];

View file

@ -0,0 +1,248 @@
/*
* 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { ML_JOB_AGGREGATION, KIBANA_AGGREGATION, ES_AGGREGATION } from './aggregation_types';
import { MLCATEGORY } from './field_types';
/**
* EVENT_RATE_FIELD_ID
*/
export const EVENT_RATE_FIELD_ID = '__ml_event_rate_count__';
/**
* METRIC_AGG_TYPE
*/
export const METRIC_AGG_TYPE = 'metrics';
/**
* Field id
*/
export type FieldId = string;
/**
* AggId is an alias of ML_JOB_AGGREGATION
*/
export type AggId = ML_JOB_AGGREGATION;
/**
* Split field can be Field or null.
*/
export type SplitField = Field | null;
/**
* Field definition
*/
export interface Field {
/**
* The field id
*/
id: FieldId;
/**
* The field name
*/
name: string;
/**
* The field type is based on ES field types.
*/
type: ES_FIELD_TYPES;
/**
* Flag whether the field is aggregatable.
*/
aggregatable: boolean;
/**
* Flag for counter.
*/
counter: boolean;
/**
* Optional array of AggId.
*/
aggIds?: AggId[];
/**
* Optional array fo aggregations.
*/
aggs?: Aggregation[];
/**
* Optional runtime field.
*/
runtimeField?: estypes.MappingRuntimeField;
}
/**
* Aggregation definition.
*/
export interface Aggregation {
/**
* The aggregation id.
*/
id: AggId;
/**
* The aggregation title.
*/
title: string;
/**
* The Kibana name for the aggregation.
*/
kibanaName: KIBANA_AGGREGATION | null;
/**
* The ES DSL name for the aggregation.
*/
dslName: ES_AGGREGATION | null;
/**
* The metric agg type.
*/
type: typeof METRIC_AGG_TYPE;
/**
* The model plot agg definition.
*/
mlModelPlotAgg: {
min: string;
max: string;
};
/**
* Optional array of field ids.
*/
fieldIds?: FieldId[];
/**
* Optional array or fields.
*/
fields?: Field[];
}
/**
* Job caps for a new job.
*/
export interface NewJobCaps {
/**
* Array of fields.
*/
fields: Field[];
/**
* Array of aggregations.
*/
aggs: Aggregation[];
}
/**
* Job caps response for a new job.
*/
export interface NewJobCapsResponse {
/**
* Index pattern
*/
[indexPattern: string]: NewJobCaps;
}
/**
* Definition for a pair of aggregation and field.
*/
export interface AggFieldPair {
/**
* The aggregation.
*/
agg: Aggregation;
/**
* The field.
*/
field: Field;
/**
* Optional by-field configuration.
*/
by?: {
field: SplitField;
value: string | null;
};
/**
* Optional over-field configuration
*/
over?: {
field: SplitField;
value: string | null;
};
/**
* Optional partition field configuration
*/
partition?: {
field: SplitField;
value: string | null;
};
/**
* Optional exclude frequent.
*/
excludeFrequent?: string;
}
/**
* Definition for a pair of aggregation and field name.
*/
export interface AggFieldNamePair {
/**
* The aggregation definition.
*/
agg: string;
/**
* The field name
*/
field: string;
/**
* Optional by-field configuration
*/
by?: {
field: string | null;
value: string | null;
};
/**
* Optional over-field configuration
*/
over?: {
field: string | null;
value: string | null;
};
/**
* Optional partition-field configuration
*/
partition?: {
field: string | null;
value: string | null;
};
/**
* Optional exclude frequent.
*/
excludeFrequent?: string;
}
/**
* Definition for an ml category.
*/
export const mlCategory: Field = {
/**
* id `mlcategory` id
*/
id: MLCATEGORY,
/**
* name `mlcategory`
*/
name: MLCATEGORY,
/**
* type `keyword`
*/
type: ES_FIELD_TYPES.KEYWORD,
/**
* non-aggregatable
*/
aggregatable: false,
/**
* no counter
*/
counter: false,
};
/**
* Rollup fields are a nested record of field ids and ES aggregations.
*/
export type RollupFields = Record<FieldId, [Record<'agg', ES_AGGREGATION>]>;

View file

@ -9,6 +9,16 @@ export { type MlSeverityType, ML_ANOMALY_SEVERITY } from './anomaly_severity';
export { ML_ANOMALY_THRESHOLD } from './anomaly_threshold';
export { ML_SEVERITY_COLORS } from './severity_colors';
export {
mlJobAggregations,
mlJobAggregationsWithoutEsEquivalent,
type SparseDataAggregation,
ES_AGGREGATION,
KIBANA_AGGREGATION,
ML_JOB_AGGREGATION,
SPARSE_DATA_AGGREGATIONS,
} from './aggregation_types';
export {
aggregationTypeTransform,
getAnomalyScoreExplanationImpactValue,
@ -50,6 +60,33 @@ export {
ML_DETECTOR_RULE_OPERATOR,
} from './detector_rule';
export type { InfluencersFilterQuery } from './es_client';
export {
type MlJobFieldType,
DOC_COUNT,
MLCATEGORY,
ML_JOB_FIELD_TYPES,
OMIT_FIELDS,
_DOC_COUNT,
} from './field_types';
export {
mlCategory,
type AggFieldNamePair,
type AggFieldPair,
type AggId,
type Aggregation,
type Field,
type FieldId,
type NewJobCaps,
type NewJobCapsResponse,
type RollupFields,
type SplitField,
EVENT_RATE_FIELD_ID,
METRIC_AGG_TYPE,
} from './fields';
export { getFormattedSeverityScore } from './get_formatted_severity_score';
export { getSeverity } from './get_severity';
export { getSeverityColor } from './get_severity_color';

View file

@ -18,5 +18,6 @@
"kbn_references": [
"@kbn/i18n",
"@kbn/ml-is-populated-object",
"@kbn/field-types",
]
}

View file

@ -26,16 +26,11 @@ export interface MlInfluencer {
/**
* Alias of MlAnomalyRecordDoc
* @export
* @typedef {MLAnomalyDoc}
*/
export type MLAnomalyDoc = MlAnomalyRecordDoc;
/**
* MlRecordForInfluencer, an alias based on MlAnomalyRecordDoc.
*
* @export
* @typedef {MlRecordForInfluencer}
*/
export type MlRecordForInfluencer = MlAnomalyRecordDoc;
@ -359,30 +354,21 @@ export interface MlAnomaliesTableRecord {
export interface MlAnomaliesTableRecordExtended extends MlAnomaliesTableRecord {
/**
* The detector name.
* @type {string}
*/
detector: string;
/**
* The length of the rule.
* @type {?number}
*/
rulesLength?: number;
}
/**
* Union type for partitiion field types.
*
* @export
* @typedef {MlPartitionFieldsType}
*/
export type MlPartitionFieldsType = typeof ML_PARTITION_FIELDS[number];
/**
* Anomaly record document for categorizer stats.
*
* @export
* @interface MlAnomalyCategorizerStatsDoc
* @typedef {MlAnomalyCategorizerStatsDoc}
*/
export interface MlAnomalyCategorizerStatsDoc {
/**
@ -393,89 +379,73 @@ export interface MlAnomalyCategorizerStatsDoc {
/**
* The identifier for the anomaly detection job.
* @type {string}
*/
job_id: string;
/**
* The type of the result document.
* @type {'categorizer_stats'}
*/
result_type: 'categorizer_stats';
/**
* The field used to segment the analysis.
* When you use this property, you have completely independent baselines for each value of this field.
* @type {?string}
*/
partition_field_name?: string;
/**
* The value of the partition field.
* @type {?string}
*/
partition_field_value?: string;
/**
* The number of documents.
* @type {number}
*/
categorized_doc_count: number;
/**
* The total number of categories.
* @type {number}
*/
total_category_count: number;
/**
* The number of frequent categories.
* @type {number}
*/
frequent_category_count: number;
/**
* The number of rare categories.
* @type {number}
*/
rare_category_count: number;
/**
* The number of dead categories.
* @type {number}
*/
dead_category_count: number;
/**
* The number of failed categories.
* @type {number}
*/
failed_category_count: number;
/**
* The categorization status.
* @type {('ok' | 'warn')}
*/
categorization_status: 'ok' | 'warn';
/**
* The log time.
* @type {number}
*/
log_time: number;
/**
* The start time of the bucket for which these results were calculated.
* @type {number}
*/
timestamp: number;
}
/**
* Union type for entity field types.
*
* @export
* @typedef {MlEntityFieldType}
*/
export type MlEntityFieldType = 'partition_field' | 'over_field' | 'by_field';

View file

@ -24,7 +24,6 @@ export {
FEATURE_IMPORTANCE,
FEATURE_INFLUENCE,
INDEX_CREATED_BY,
INDEX_STATUS,
JOB_MAP_NODE_TYPES,
NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN,
OUTLIER_ANALYSIS_METHOD,

View file

@ -7,8 +7,6 @@
/**
* Custom enum for DFA config types
*
* @type {{ readonly OUTLIER_DETECTION: "outlier_detection"; readonly REGRESSION: "regression"; readonly CLASSIFICATION: "classification"; }}
*/
export const ANALYSIS_CONFIG_TYPE = {
OUTLIER_DETECTION: 'outlier_detection',
@ -18,8 +16,6 @@ export const ANALYSIS_CONFIG_TYPE = {
/**
* Custom enum for DFA task states
*
* @type {{ readonly ANALYZING: "analyzing"; readonly FAILED: "failed"; readonly REINDEXING: "reindexing"; readonly STARTED: "started"; readonly STARTING: "starting"; readonly STOPPED: "stopped"; }}
*/
export const DATA_FRAME_TASK_STATE = {
ANALYZING: 'analyzing',
@ -32,15 +28,11 @@ export const DATA_FRAME_TASK_STATE = {
/**
* Default results field
*
* @type {"ml"}
*/
export const DEFAULT_RESULTS_FIELD = 'ml';
/**
* Custom enum for job map node types for the DFA map view
*
* @type {{ readonly ANALYTICS: "analytics"; readonly TRANSFORM: "transform"; readonly INDEX: "index"; readonly TRAINED_MODEL: "trainedModel"; }}
*/
export const JOB_MAP_NODE_TYPES = {
ANALYTICS: 'analytics',
@ -51,16 +43,11 @@ export const JOB_MAP_NODE_TYPES = {
/**
* Union type of JOB_MAP_NODE_TYPES
*
* @export
* @typedef {JobMapNodeTypes}
*/
export type JobMapNodeTypes = typeof JOB_MAP_NODE_TYPES[keyof typeof JOB_MAP_NODE_TYPES];
/**
* Custom enum for the metadata to be stored about which tool was used to create an index
*
* @type {{ readonly FILE_DATA_VISUALIZER: "file-data-visualizer"; readonly DATA_FRAME_ANALYTICS: "data-frame-analytics"; }}
*/
export const INDEX_CREATED_BY = {
FILE_DATA_VISUALIZER: 'file-data-visualizer',
@ -69,37 +56,26 @@ export const INDEX_CREATED_BY = {
/**
* Feature importance constant
*
* @type {"feature_importance"}
*/
export const FEATURE_IMPORTANCE = 'feature_importance';
/**
* Feature influence constant
*
* @type {"feature_influence"}
*/
export const FEATURE_INFLUENCE = 'feature_influence';
/**
* Top classes constant
*
* @type {"top_classes"}
*/
export const TOP_CLASSES = 'top_classes';
/**
* Outlier score constant
*
* @type {"outlier_score"}
*/
export const OUTLIER_SCORE = 'outlier_score';
/**
* Enum for a DFA configuration's advanced fields
*
* @export
* @enum {number}
*/
export enum ANALYSIS_ADVANCED_FIELDS {
ALPHA = 'alpha',
@ -124,9 +100,6 @@ export enum ANALYSIS_ADVANCED_FIELDS {
/**
* Enum for a DFA configuration's outlier analysis method
*
* @export
* @enum {number}
*/
export enum OUTLIER_ANALYSIS_METHOD {
LOF = 'lof',
@ -137,34 +110,15 @@ export enum OUTLIER_ANALYSIS_METHOD {
/**
* Minimum value for feature importance
*
* @type {0}
*/
export const NUM_TOP_FEATURE_IMPORTANCE_VALUES_MIN = 0;
/**
* Minimum training percent
*
* @type {1}
*/
export const TRAINING_PERCENT_MIN = 1;
/**
* Maximum training percent
*
* @type {100}
*/
export const TRAINING_PERCENT_MAX = 100;
/**
* Enum for index status
*
* @export
* @enum {number}
*/
export enum INDEX_STATUS {
UNUSED,
LOADING,
LOADED,
ERROR,
}

View file

@ -9,28 +9,19 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object';
/**
* Union type for ES result feature importance class name
*
* @export
* @typedef {FeatureImportanceClassName}
*/
export type FeatureImportanceClassName = string | number | boolean;
/**
* ES result class feature importance
*
* @export
* @interface ClassFeatureImportance
* @typedef {ClassFeatureImportance}
*/
export interface ClassFeatureImportance {
/**
* The class name
* @type {FeatureImportanceClassName}
*/
class_name: FeatureImportanceClassName;
/**
* The importance
* @type {number}
*/
importance: number;
}
@ -39,82 +30,55 @@ export interface ClassFeatureImportance {
* ES result feature importance interface
* TODO We should separate the interface because classes/importance
* isn't both optional but either/or.
*
* @export
* @interface FeatureImportance
* @typedef {FeatureImportance}
*/
export interface FeatureImportance {
/**
* The feature name
* @type {string}
*/
feature_name: string;
/**
* Optional classes
* @type {?ClassFeatureImportance[]}
*/
classes?: ClassFeatureImportance[];
/**
* Optional importance
* @type {?number}
*/
importance?: number;
}
/**
* ES result top class interface
*
* @export
* @interface TopClass
* @typedef {TopClass}
*/
export interface TopClass {
/**
* The class name
* @type {FeatureImportanceClassName}
*/
class_name: FeatureImportanceClassName;
/**
* The class probability
* @type {number}
*/
class_probability: number;
/**
* The class score
* @type {number}
*/
class_score: number;
}
/**
* Array of TopClass
*
* @export
* @typedef {TopClasses}
*/
export type TopClasses = TopClass[];
/**
* ES result for class feature importance summary
*
* @export
* @interface ClassFeatureImportanceSummary
* @typedef {ClassFeatureImportanceSummary}
*/
export interface ClassFeatureImportanceSummary {
/**
* The class name
* @type {FeatureImportanceClassName}
*/
class_name: FeatureImportanceClassName;
/**
* The importance
* @type {{
max: number;
min: number;
mean_magnitude: number;
}}
*/
importance: {
max: number;
@ -124,73 +88,51 @@ export interface ClassFeatureImportanceSummary {
}
/**
* ES result classification total feature importance
*
* @export
* @interface ClassificationTotalFeatureImportance
* @typedef {ClassificationTotalFeatureImportance}
*/
export interface ClassificationTotalFeatureImportance {
/**
* The feature name
* @type {string}
*/
feature_name: string;
/**
* The classes, array of ClassFeatureImportanceSummary
* @type {ClassFeatureImportanceSummary[]}
*/
classes: ClassFeatureImportanceSummary[];
}
/**
* ES result regression feature importance summary
*
* @export
* @interface RegressionFeatureImportanceSummary
* @typedef {RegressionFeatureImportanceSummary}
*/
export interface RegressionFeatureImportanceSummary {
/**
* Max feature importance
* @type {number}
*/
max: number;
/**
* Min feature importance
* @type {number}
*/
min: number;
/**
* Mean magnitude
* @type {number}
*/
mean_magnitude: number;
}
/**
* ES result for regression total feature importance
*
* @export
* @interface RegressionTotalFeatureImportance
* @typedef {RegressionTotalFeatureImportance}
*/
export interface RegressionTotalFeatureImportance {
/**
* Feature name
* @type {string}
*/
feature_name: string;
/**
* Importance
* @type {RegressionFeatureImportanceSummary}
*/
importance: RegressionFeatureImportanceSummary;
}
/**
* Union type of total feature importance types
*
* @export
* @typedef {TotalFeatureImportance}
*/
export type TotalFeatureImportance =
| ClassificationTotalFeatureImportance
@ -198,58 +140,39 @@ export type TotalFeatureImportance =
/**
* Baseline interface for ES result feature importance class
*
* @export
* @interface FeatureImportanceClassBaseline
* @typedef {FeatureImportanceClassBaseline}
*/
export interface FeatureImportanceClassBaseline {
/**
* Class name
* @type {FeatureImportanceClassName}
*/
class_name: FeatureImportanceClassName;
/**
* Baseline
* @type {number}
*/
baseline: number;
}
/**
* Baseline interface for ES result classification feature importance
*
* @export
* @interface ClassificationFeatureImportanceBaseline
* @typedef {ClassificationFeatureImportanceBaseline}
*/
export interface ClassificationFeatureImportanceBaseline {
/**
* Classes
* @type {FeatureImportanceClassBaseline[]}
*/
classes: FeatureImportanceClassBaseline[];
}
/**
* Baseline interface for ES result regression feature importance
*
* @export
* @interface RegressionFeatureImportanceBaseline
* @typedef {RegressionFeatureImportanceBaseline}
*/
export interface RegressionFeatureImportanceBaseline {
/**
* Baseline
* @type {number}
*/
baseline: number;
}
/**
* Union type of feature importance baseline types
*
* @export
* @typedef {FeatureImportanceBaseline}
*/
export type FeatureImportanceBaseline =
| ClassificationFeatureImportanceBaseline
@ -258,7 +181,6 @@ export type FeatureImportanceBaseline =
/**
* Type guard for total feature importance
*
* @export
* @param {unknown} arg The feature importance to identify
* @returns {arg is ClassificationTotalFeatureImportance}
*/
@ -271,7 +193,6 @@ export function isClassificationTotalFeatureImportance(
/**
* Type guard for regression total feature importance
*
* @export
* @param {unknown} arg The feature importance to identify
* @returns {arg is RegressionTotalFeatureImportance}
*/
@ -284,7 +205,6 @@ export function isRegressionTotalFeatureImportance(
/**
* Type guard for classification feature importance baseline
*
* @export
* @param {unknown} arg The baseline to identify
* @returns {arg is ClassificationFeatureImportanceBaseline}
*/
@ -297,7 +217,6 @@ export function isClassificationFeatureImportanceBaseline(
/**
* Type guard for regression feature importance baseline
*
* @export
* @param {unknown} arg The baseline to identify
* @returns {arg is RegressionFeatureImportanceBaseline}
*/

View file

@ -19,64 +19,45 @@ import { DataFrameAnalyticsConfig } from './types';
/**
* ES id _id
*
* @export
* @typedef {EsId}
*/
export type EsId = string;
/**
* ES source _source
*
* @export
* @typedef {EsDocSource}
*/
export type EsDocSource = Record<string, any>;
/**
* ES field name
*
* @export
* @typedef {EsFieldName}
*/
export type EsFieldName = string;
/**
* ES doc
*
* @export
* @interface EsDoc
* @typedef {EsDoc}
* @extends {Record<string, any>}
*/
export interface EsDoc extends Record<string, any> {
/**
* ES _id
* @type {EsId}
*/
_id: EsId;
/**
* ES _source
* @type {EsDocSource}
*/
_source: EsDocSource;
}
/**
* Max columns
* @type {10}
*/
export const MAX_COLUMNS = 10;
/**
* Default regression columns
* @type {8}
*/
export const DEFAULT_REGRESSION_COLUMNS = 8;
/**
* Set of basic numerical types
* @type {*}
*/
export const BASIC_NUMERICAL_TYPES = new Set([
ES_FIELD_TYPES.UNSIGNED_LONG,
@ -88,7 +69,6 @@ export const BASIC_NUMERICAL_TYPES = new Set([
/**
* Set of extended numerical types
* @type {*}
*/
export const EXTENDED_NUMERICAL_TYPES = new Set([
ES_FIELD_TYPES.DOUBLE,
@ -99,14 +79,12 @@ export const EXTENDED_NUMERICAL_TYPES = new Set([
/**
* ES field name for copy of the doc _id
* @type {"ml__id_copy"}
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const ML__ID_COPY = 'ml__id_copy';
/**
* ES field name for ML's incremental id
* @type {"ml__incremental_id"}
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const ML__INCREMENTAL_ID = 'ml__incremental_id';

View file

@ -15,44 +15,30 @@ import { ANALYSIS_CONFIG_TYPE } from './constants';
/**
* Interface for DFA API response for deletion status
*
* @export
* @interface DeleteDataFrameAnalyticsWithIndexStatus
* @typedef {DeleteDataFrameAnalyticsWithIndexStatus}
*/
export interface DeleteDataFrameAnalyticsWithIndexStatus {
/**
* Success
* @type {boolean}
*/
success: boolean;
/**
* Optional error
* @type {?(EsErrorBody | Boom.Boom)}
*/
error?: EsErrorBody | Boom.Boom;
}
/**
* Index name
* @export
* @typedef {IndexName}
*/
export type IndexName = string;
/**
* Data frame analytics id
* @export
* @typedef {DataFrameAnalyticsId}
*/
export type DataFrameAnalyticsId = string;
/**
* Interface for outlier analysis job configuation
*
* @export
* @interface OutlierAnalysis
* @typedef {OutlierAnalysis}
*/
export interface OutlierAnalysis {
/**
@ -62,9 +48,6 @@ export interface OutlierAnalysis {
/**
* Outlier detection options
* @type {{
compute_feature_influence?: boolean;
}}
*/
outlier_detection: {
compute_feature_influence?: boolean;
@ -73,78 +56,58 @@ export interface OutlierAnalysis {
/**
* Inner interface for regression job configuration options
*
* @interface Regression
* @typedef {Regression}
*/
interface Regression {
/**
* Dependent variable
* @type {string}
*/
dependent_variable: string;
/**
* Training percent
* @type {number}
*/
training_percent: number;
/**
* Optional number of top feature importance values
* @type {?number}
*/
num_top_feature_importance_values?: number;
/**
* Prediction field name
* @type {?string}
*/
prediction_field_name?: string;
}
/**
* Inner interface for classification job configuration options
*
* @interface Classification
* @typedef {Classification}
*/
interface Classification {
/**
* Optional class assignment objective
* @type {?string}
*/
class_assignment_objective?: string;
/**
* Dependent variable
* @type {string}
*/
dependent_variable: string;
/**
* Training percent
* @type {number}
*/
training_percent: number;
/**
* Optional number of top classes
* @type {?number}
*/
num_top_classes?: number;
/**
* Optional number of top feature importance values
* @type {?number}
*/
num_top_feature_importance_values?: number;
/**
* Optional prediction field name
* @type {?string}
*/
prediction_field_name?: string;
}
/**
* Interface for regression analysis job configuation
*
* @export
* @interface RegressionAnalysis
* @typedef {RegressionAnalysis}
*/
export interface RegressionAnalysis {
/**
@ -154,17 +117,12 @@ export interface RegressionAnalysis {
/**
* Regression options
* @type {Regression}
*/
regression: Regression;
}
/**
* Interface for classification job configuation
*
* @export
* @interface ClassificationAnalysis
* @typedef {ClassificationAnalysis}
*/
export interface ClassificationAnalysis {
/**
@ -174,30 +132,21 @@ export interface ClassificationAnalysis {
/**
* Outlier detection options
* @type {Classification}
*/
classification: Classification;
}
/**
* Alias of estypes.MlDataframeAnalysisContainer
*
* @export
* @typedef {AnalysisConfig}
*/
export type AnalysisConfig = estypes.MlDataframeAnalysisContainer;
/**
* Meta data for a DFA job
*
* @export
* @interface DataFrameAnalyticsMeta
* @typedef {DataFrameAnalyticsMeta}
*/
export interface DataFrameAnalyticsMeta {
/**
* Optional custom urls
* @type {?MlUrlConfig[]}
*/
custom_urls?: MlUrlConfig[];
/**
@ -208,57 +157,41 @@ export interface DataFrameAnalyticsMeta {
/**
* Interface for a DFA config with fix for estypes provided types
*
* @export
* @interface DataFrameAnalyticsConfig
* @typedef {DataFrameAnalyticsConfig}
* @extends {Omit<estypes.MlDataframeAnalyticsSummary, 'analyzed_fields'>}
*/
export interface DataFrameAnalyticsConfig
extends Omit<estypes.MlDataframeAnalyticsSummary, 'analyzed_fields'> {
/**
* Optional analyzed fields
* @type {?estypes.MlDataframeAnalysisAnalyzedFields}
*/
analyzed_fields?: estypes.MlDataframeAnalysisAnalyzedFields;
/**
* Optional meta data
* @type {?DataFrameAnalyticsMeta}
*/
_meta?: DataFrameAnalyticsMeta;
}
/**
* Interface for a requect object to update a DFA job
*
* @export
* @interface UpdateDataFrameAnalyticsConfig
* @typedef {UpdateDataFrameAnalyticsConfig}
*/
export interface UpdateDataFrameAnalyticsConfig {
/**
* Optional allow lazy start
* @type {?string}
*/
allow_lazy_start?: string;
/**
* Optional description
* @type {?string}
*/
description?: string;
/**
* Optional model memory limit
* @type {?string}
*/
model_memory_limit?: string;
/**
* Optional max num threads
* @type {?number}
*/
max_num_threads?: number;
/**
* Optional meta data
* @type {?DataFrameAnalyticsMeta}
*/
_meta?: DataFrameAnalyticsMeta;
}
@ -266,7 +199,6 @@ export interface UpdateDataFrameAnalyticsConfig {
/**
* Type guard for a DFA config
*
* @export
* @param {unknown} arg The config to identify
* @returns {arg is DataFrameAnalyticsConfig}
*/
@ -276,170 +208,108 @@ export function isDataFrameAnalyticsConfigs(arg: unknown): arg is DataFrameAnaly
/**
* Union type of DFA anlaysis config types
*
* @export
* @typedef {DataFrameAnalysisConfigType}
*/
export type DataFrameAnalysisConfigType =
typeof ANALYSIS_CONFIG_TYPE[keyof typeof ANALYSIS_CONFIG_TYPE];
/**
* Union type of DFA task states
*
* @export
* @typedef {DataFrameTaskStateType}
*/
export type DataFrameTaskStateType = estypes.MlDataframeState | 'analyzing' | 'reindexing';
/**
* Interface for DFA stats
*
* @export
* @interface DataFrameAnalyticsStats
* @typedef {DataFrameAnalyticsStats}
* @extends {Omit<estypes.MlDataframeAnalytics, 'state'>}
*/
export interface DataFrameAnalyticsStats extends Omit<estypes.MlDataframeAnalytics, 'state'> {
/**
* Optional failure reason
* @type {?string}
*/
failure_reason?: string;
/**
* Task state
* @type {DataFrameTaskStateType}
*/
state: DataFrameTaskStateType;
}
/**
* Alias for estypes.MlExplainDataFrameAnalyticsResponse
*
* @export
* @typedef {DfAnalyticsExplainResponse}
*/
export type DfAnalyticsExplainResponse = estypes.MlExplainDataFrameAnalyticsResponse;
/**
* Interface for predicted class
*
* @export
* @interface PredictedClass
* @typedef {PredictedClass}
*/
export interface PredictedClass {
/**
* Predicted class
* @type {string}
*/
predicted_class: string;
/**
* Count
* @type {number}
*/
count: number;
}
/**
* Interface for confusion matrix
*
* @export
* @interface ConfusionMatrix
* @typedef {ConfusionMatrix}
*/
export interface ConfusionMatrix {
/**
* Actual class
* @type {string}
*/
actual_class: string;
/**
* Actual class doc count
* @type {number}
*/
actual_class_doc_count: number;
/**
* Array of predicted classes
* @type {PredictedClass[]}
*/
predicted_classes: PredictedClass[];
/**
* Doc count of other predicted classes
* @type {number}
*/
other_predicted_class_doc_count: number;
}
/**
* Data item for ROC curve
*
* @export
* @interface RocCurveItem
* @typedef {RocCurveItem}
*/
export interface RocCurveItem {
/**
* FPR
* @type {number}
*/
fpr: number;
/**
* Threshold
* @type {number}
*/
threshold: number;
/**
* TPR
* @type {number}
*/
tpr: number;
}
/**
* Eval Class
* @interface EvalClass
* @typedef {EvalClass}
*/
interface EvalClass {
/**
* Class name
* @type {string}
*/
class_name: string;
/**
* Value
* @type {number}
*/
value: number;
}
/**
* Interface for classification evaluate response
*
* @export
* @interface ClassificationEvaluateResponse
* @typedef {ClassificationEvaluateResponse}
*/
export interface ClassificationEvaluateResponse {
/**
* Classificatio evaluation
* @type {{
multiclass_confusion_matrix?: {
confusion_matrix: ConfusionMatrix[];
};
recall?: {
classes: EvalClass[];
avg_recall: number;
};
accuracy?: {
classes: EvalClass[];
overall_accuracy: number;
};
auc_roc?: {
curve?: RocCurveItem[];
value: number;
};
}}
* Classification evaluation
*/
classification: {
multiclass_confusion_matrix?: {
@ -462,20 +332,10 @@ export interface ClassificationEvaluateResponse {
/**
* Interface for evalute metrics
*
* @export
* @interface EvaluateMetrics
* @typedef {EvaluateMetrics}
*/
export interface EvaluateMetrics {
/**
* Classification evalute metrics
* @type {{
accuracy?: object;
recall?: object;
multiclass_confusion_matrix?: object;
auc_roc?: { include_curve: boolean; class_name: string };
}}
*/
classification: {
accuracy?: object;
@ -485,12 +345,6 @@ export interface EvaluateMetrics {
};
/**
* Regression evaluate metrics
* @type {{
r_squared: object;
mse: object;
msle: object;
huber: object;
}}
*/
regression: {
r_squared: object;
@ -502,11 +356,6 @@ export interface EvaluateMetrics {
/**
* Interface for field selection item
*
* @export
* @interface FieldSelectionItem
* @typedef {FieldSelectionItem}
* @extends {Omit<estypes.MlDataframeAnalyticsFieldSelection, 'mapping_types'>}
*/
export interface FieldSelectionItem
extends Omit<estypes.MlDataframeAnalyticsFieldSelection, 'mapping_types'> {
@ -519,20 +368,10 @@ export interface FieldSelectionItem
/**
* Interface for a node element for the map view
*
* @export
* @interface AnalyticsMapNodeElement
* @typedef {AnalyticsMapNodeElement}
*/
export interface AnalyticsMapNodeElement {
/**
* Inner data of the node element
* @type {{
id: string;
label: string;
type: string;
analysisType?: string;
}}
*/
data: {
id: string;
@ -544,19 +383,10 @@ export interface AnalyticsMapNodeElement {
/**
* Interface for an edge element for the map view
*
* @export
* @interface AnalyticsMapEdgeElement
* @typedef {AnalyticsMapEdgeElement}
*/
export interface AnalyticsMapEdgeElement {
/**
* Inner data of the edge element
* @type {{
id: string;
source: string;
target: string;
}}
*/
data: {
id: string;
@ -567,62 +397,38 @@ export interface AnalyticsMapEdgeElement {
/**
* Union type of map node and edge elements
*
* @export
* @typedef {MapElements}
*/
export type MapElements = AnalyticsMapNodeElement | AnalyticsMapEdgeElement;
/**
* Interface for DFA map return type
*
* @export
* @interface AnalyticsMapReturnType
* @typedef {AnalyticsMapReturnType}
*/
export interface AnalyticsMapReturnType {
/**
* Map elements
* @type {MapElements[]}
*/
elements: MapElements[];
/**
* Transform, job or index details
* @type {Record<string, any>}
*/
details: Record<string, any>;
/**
* Error
* @type {(null | any)}
*/
error: null | any;
}
/**
* Alias for estypes.MlDataframeAnalysisFeatureProcessor
*
* @export
* @typedef {FeatureProcessor}
*/
export type FeatureProcessor = estypes.MlDataframeAnalysisFeatureProcessor;
/**
* Interface for a search response's track total hits option
*
* @export
* @interface TrackTotalHitsSearchResponse
* @typedef {TrackTotalHitsSearchResponse}
*/
export interface TrackTotalHitsSearchResponse {
/**
* Inner structure of the response
* @type {{
total: {
value: number;
relation: string;
};
hits: any[];
}}
*/
hits: {
total: {

View file

@ -0,0 +1,3 @@
# @kbn/ml-data-grid
This package contains a custom data grid component that is used by the ML and Transform plugins. On top of `EuiDataGrid` this extended, version supports a custom header with mini histograms for each column.

View file

@ -13,9 +13,9 @@ import { euiTextTruncate, type EuiDataGridColumn } from '@elastic/eui';
import { euiThemeVars } from '@kbn/ui-theme';
import { isUnsupportedChartData, ChartData } from '../../../../common/types/field_histograms';
import { isUnsupportedChartData, ChartData } from '../lib/field_histograms';
import { useColumnChart } from './use_column_chart';
import { useColumnChart } from '../hooks/use_column_chart';
const cssHistogram = css({
width: '100%',

View file

@ -6,7 +6,7 @@
*/
import { isEqual } from 'lodash';
import React, { memo, useEffect, useCallback, useRef, FC } from 'react';
import React, { memo, useEffect, useRef, FC } from 'react';
import { css } from '@emotion/react';
import {
@ -16,8 +16,8 @@ import {
EuiCodeBlock,
EuiCopy,
EuiDataGrid,
EuiDataGridProps,
EuiDataGridCellPopoverElementProps,
EuiDataGridProps,
EuiFlexGroup,
EuiFlexItem,
EuiMutationObserver,
@ -29,30 +29,11 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { CoreSetup } from '@kbn/core/public';
import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import {
type DataFrameAnalysisConfigType,
type FeatureImportanceBaseline,
type FeatureImportance,
type TopClasses,
ANALYSIS_CONFIG_TYPE,
DEFAULT_RESULTS_FIELD,
INDEX_STATUS,
} from '@kbn/ml-data-frame-analytics-utils';
import { DEFAULT_SAMPLER_SHARD_SIZE } from '../../../../common/constants/field_histograms';
import {
euiDataGridStyle,
euiDataGridToolbarSettings,
getFeatureImportance,
getTopClasses,
} from './common';
import { UseIndexDataReturnType } from './types';
import { DecisionPathPopover } from '../../data_frame_analytics/pages/analytics_exploration/components/feature_importance/decision_path_popover';
// TODO Fix row hovering + bar highlighting
// import { hoveredRow$ } from './column_chart';
import { euiDataGridStyle, euiDataGridToolbarSettings, INDEX_STATUS } from '../lib/common';
import { UseIndexDataReturnType } from '../lib/types';
const cssOverride = css({
'.euiDataGridRowCell--boolean': { textTransform: 'none' },
@ -74,10 +55,8 @@ export const DataGridTitle: FC<{ title: string }> = ({ title }) => (
);
interface PropsWithoutHeader extends UseIndexDataReturnType {
baseline?: FeatureImportanceBaseline;
analysisType?: DataFrameAnalysisConfigType | 'unknown';
resultsField?: string;
dataTestSubj: string;
renderCellPopover?: (popoverProps: EuiDataGridCellPopoverElementProps) => JSX.Element;
toastNotifications: CoreSetup['notifications']['toasts'];
trailingControlColumns?: EuiDataGridProps['trailingControlColumns'];
}
@ -88,16 +67,18 @@ interface PropsWithHeader extends PropsWithoutHeader {
title: string;
}
function isWithHeader(arg: any): arg is PropsWithHeader {
return typeof arg?.title === 'string' && arg?.title !== '';
function isWithHeader(arg: unknown): arg is PropsWithHeader {
return isPopulatedObject(arg, ['title']) && typeof arg.title === 'string' && arg.title !== '';
}
type Props = PropsWithHeader | PropsWithoutHeader;
/**
* Custom data grid component with support for mini histograms.
*/
export const DataGrid: FC<Props> = memo(
(props) => {
const {
baseline,
chartsVisible,
chartsButtonVisible,
ccsWarning,
@ -111,6 +92,7 @@ export const DataGrid: FC<Props> = memo(
onSort,
pagination,
setVisibleColumns,
renderCellPopover,
renderCellValue,
rowCount,
sortingColumns,
@ -119,83 +101,8 @@ export const DataGrid: FC<Props> = memo(
toastNotifications,
toggleChartVisibility,
visibleColumns,
predictionFieldName,
resultsField,
analysisType,
trailingControlColumns,
} = props;
// TODO Fix row hovering + bar highlighting
// const getRowProps = (item: any) => {
// return {
// onMouseOver: () => hoveredRow$.next(item),
// onMouseLeave: () => hoveredRow$.next(null),
// };
// };
const renderCellPopover = useCallback(
(popoverProps: EuiDataGridCellPopoverElementProps) => {
const { schema, rowIndex, cellContentsElement, DefaultCellPopover } = popoverProps;
if (
analysisType === ANALYSIS_CONFIG_TYPE.REGRESSION ||
analysisType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION ||
analysisType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION
) {
if (schema === 'featureImportance') {
const row = data[rowIndex - pagination.pageIndex * pagination.pageSize];
if (!row) return <div />;
// if resultsField for some reason is not available then use ml
const mlResultsField = resultsField ?? DEFAULT_RESULTS_FIELD;
let predictedValue: string | number | undefined;
let predictedProbability: number | undefined;
let topClasses: TopClasses = [];
if (
predictionFieldName !== undefined &&
row &&
row[`${mlResultsField}.${predictionFieldName}`] !== undefined
) {
predictedValue = row[`${mlResultsField}.${predictionFieldName}`];
topClasses = getTopClasses(row, mlResultsField);
predictedProbability = row[`${mlResultsField}.prediction_probability`];
}
const isClassTypeBoolean = topClasses.reduce(
(p, c) => typeof c.class_name === 'boolean' || p,
false
);
const parsedFIArray: FeatureImportance[] = getFeatureImportance(
row,
mlResultsField,
isClassTypeBoolean
);
return (
<div data-test-subj="mlDFAFeatureImportancePopover">
<DecisionPathPopover
analysisType={analysisType}
predictedValue={predictedValue}
predictedProbability={predictedProbability}
baseline={baseline}
featureImportance={parsedFIArray}
topClasses={topClasses}
predictionFieldName={
predictionFieldName ? predictionFieldName.replace('_prediction', '') : undefined
}
/>
</div>
);
} else if (schema === 'featureInfluence') {
return <EuiCodeBlock isCopyable={true}>{cellContentsElement.textContent}</EuiCodeBlock>;
} else {
return <DefaultCellPopover {...popoverProps} />;
}
} else {
return <DefaultCellPopover {...popoverProps} />;
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[baseline, data]
);
useEffect(() => {
if (invalidSortingColumnns.length > 0) {

View file

@ -19,7 +19,7 @@ import {
NumericChartData,
OrdinalChartData,
UnsupportedChartData,
} from '../../../../common/types/field_histograms';
} from '../lib/field_histograms';
import { getFieldType, getLegendText, getXScaleType, useColumnChart } from './use_column_chart';

View file

@ -25,9 +25,10 @@ import {
ChartDataItem,
NumericDataItem,
OrdinalDataItem,
} from '../../../../common/types/field_histograms';
} from '../lib/field_histograms';
import { NON_AGGREGATABLE } from './common';
import { NON_AGGREGATABLE } from '../lib/common';
import { DataGridItem } from '../lib/types';
const cssHistogramLegendBoolean = css({
width: '100%',
@ -41,7 +42,7 @@ const cssTextAlignCenter = css({
textAlign: 'center',
});
export const hoveredRow$ = new BehaviorSubject<any | null>(null);
export const hoveredRow$ = new BehaviorSubject<DataGridItem | null>(null);
export const BAR_COLOR = euiPaletteColorBlind()[0];
const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10];
@ -61,6 +62,12 @@ export const getXScaleType = (kbnFieldType: KBN_FIELD_TYPES | undefined): XScale
}
};
/**
* Gets the Kibana field type from a EUI data grid column schema.
*
* @param {EuiDataGridColumn['schema']} schema - EUI data grid column schema.
* @returns {(KBN_FIELD_TYPES | undefined)}
*/
export const getFieldType = (schema: EuiDataGridColumn['schema']): KBN_FIELD_TYPES | undefined => {
if (schema === NON_AGGREGATABLE) {
return undefined;
@ -152,6 +159,14 @@ interface ColumnChart {
xScaleType: XScaleType;
}
/**
* Custom hook to manage state of a DataGrid column chart.
*
* @param {ChartData} chartData - The original chart data to be transformed into the ColumnChart's format.
* @param {EuiDataGridColumn} columnType - EUI column type.
* @param {?number} [maxChartColumns] - Maximum number of chart columns.
* @returns {ColumnChart}
*/
export const useColumnChart = (
chartData: ChartData,
columnType: EuiDataGridColumn,

View file

@ -9,13 +9,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui';
import { INDEX_STATUS } from '@kbn/ml-data-frame-analytics-utils';
import { ES_CLIENT_TOTAL_HITS_RELATION } from '@kbn/ml-query-utils';
import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../../common/types/es_client';
import { ChartData } from '../../../../common/types/field_histograms';
import { ColumnChart } from './column_chart';
import { COLUMN_CHART_DEFAULT_VISIBILITY_ROWS_THRESHOLD, INIT_MAX_COLUMNS } from './common';
import { INDEX_STATUS } from '../lib/common';
import { ChartData } from '../lib/field_histograms';
import { ColumnChart } from '../components/column_chart';
import { COLUMN_CHART_DEFAULT_VISIBILITY_ROWS_THRESHOLD, INIT_MAX_COLUMNS } from '../lib/common';
import {
ChartsVisible,
ColumnId,
@ -26,13 +25,22 @@ import {
OnSort,
RowCountInfo,
UseDataGridReturnType,
} from './types';
} from '../lib/types';
const rowCountDefault: RowCountInfo = {
rowCount: 0,
rowCountRelation: undefined,
};
/**
* Custom hook to manage DataGrid state.
*
* @param {EuiDataGridColumn[]} columns - EUI column spec
* @param {number} [defaultPageSize=5] - Default page size
* @param {number} [defaultVisibleColumnsCount=INIT_MAX_COLUMNS] - Default count of visible columns
* @param {?(id: string) => boolean} [defaultVisibleColumnsFilter] - Optional external columns filter
* @returns {UseDataGridReturnType}
*/
export const useDataGrid = (
columns: EuiDataGridColumn[],
defaultPageSize = 5,

View file

@ -9,22 +9,46 @@ export {
getDataGridSchemasFromFieldTypes,
getDataGridSchemaFromESFieldType,
getDataGridSchemaFromKibanaFieldType,
getFeatureImportance,
getFieldsFromKibanaIndexPattern,
getCombinedRuntimeMappings,
multiColumnSortFactory,
getNestedOrEscapedVal,
getProcessedFields,
getTopClasses,
multiColumnSortFactory,
showDataGridColumnChartErrorMessageToast,
useRenderCellValue,
getProcessedFields,
} from './common';
export { getFieldType } from './use_column_chart';
export { useDataGrid } from './use_data_grid';
export { DataGrid } from './data_grid';
INDEX_STATUS,
INIT_MAX_COLUMNS,
type FieldTypes,
type MultiColumnSorter,
} from './lib/common';
export { DataGrid } from './components/data_grid';
export {
isNumericChartData,
isOrdinalChartData,
isUnsupportedChartData,
type ChartData,
type ChartDataItem,
type NumericChartData,
type NumericDataItem,
type OrdinalChartData,
type OrdinalDataItem,
type UnsupportedChartData,
} from './lib/field_histograms';
export type {
DataGridItem,
IndexPagination,
EsSorting,
RenderCellValue,
RowCountRelation,
UseDataGridReturnType,
UseIndexDataReturnType,
} from './types';
RowCountInfo,
} from './lib/types';
export { getFieldType } from './hooks/use_column_chart';
export { useDataGrid } from './hooks/use_data_grid';

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { ML_JOB_FIELD_TYPES } from '../constants/field_types';
export type MlJobFieldType = typeof ML_JOB_FIELD_TYPES[keyof typeof ML_JOB_FIELD_TYPES];
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/ml/data_grid'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-data-grid",
"owner": "@elastic/ml-ui"
}

View file

@ -20,7 +20,7 @@ const data = [
{ s: 'b', n: 4 },
];
describe('Data Frame Analytics: Data Grid Common', () => {
describe('Data Grid Common', () => {
describe('multiColumnSortFactory', () => {
it('returns desc sorted by one column', () => {
const sortingColumns1: MultiColumnSorter[] = [{ id: 's', direction: 'desc', type: 'number' }];

View file

@ -6,19 +6,19 @@
*/
import moment from 'moment-timezone';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { useMemo } from 'react';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { EuiDataGridCellValueElementProps, EuiDataGridStyle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CoreSetup } from '@kbn/core/public';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import type { FieldFormat } from '@kbn/field-formats-plugin/common';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types';
import { getNestedProperty } from '@kbn/ml-nested-property';
import { isCounterTimeSeriesMetric } from '@kbn/ml-agg-utils';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils';
import {
type FeatureImportance,
type FeatureImportanceClassName,
@ -31,17 +31,34 @@ import {
OUTLIER_SCORE,
TOP_CLASSES,
} from '@kbn/ml-data-frame-analytics-utils';
import { extractErrorMessage, type ErrorType } from '@kbn/ml-error-utils';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils';
import { mlFieldFormatService } from '../../services/field_format_service';
import { DataGridItem, IndexPagination, RenderCellValue } from './types';
import { RuntimeMappings } from '../../../../common/types/fields';
import { isRuntimeMappings } from '../../../../common/util/runtime_field_utils';
import type { DataGridItem, IndexPagination, RenderCellValue } from './types';
/**
* The initial maximum number of columns for the data grid.
*/
export const INIT_MAX_COLUMNS = 10;
/**
* The default threshold value for the number of rows at which the column chart visibility is set to true.
*/
export const COLUMN_CHART_DEFAULT_VISIBILITY_ROWS_THRESHOLD = 10000;
/**
* Enum for index status
*/
export enum INDEX_STATUS {
UNUSED,
LOADING,
LOADED,
ERROR,
}
/**
* Style configuration for the EuiDataGrid component.
*/
export const euiDataGridStyle: EuiDataGridStyle = {
border: 'all',
fontSize: 's',
@ -51,6 +68,9 @@ export const euiDataGridStyle: EuiDataGridStyle = {
header: 'shade',
};
/**
* Configuration settings for the EuiDataGrid toolbar.
*/
export const euiDataGridToolbarSettings = {
showColumnSelector: true,
showDisplaySelector: false,
@ -58,10 +78,15 @@ export const euiDataGridToolbarSettings = {
showFullScreenSelector: false,
};
export const getFieldsFromKibanaIndexPattern = (indexPattern: DataView): string[] => {
const allFields = indexPattern.fields.map((f) => f.name);
const indexPatternFields: string[] = allFields.filter((f) => {
if (indexPattern.metaFields.includes(f)) {
/**
* Retrieves fields from a Kibana data view.
* @param {DataView} dataView - The Kibana data view.
* @returns {string[]} - The array of field names from the data view.
*/
export const getFieldsFromKibanaIndexPattern = (dataView: DataView): string[] => {
const allFields = dataView.fields.map((f) => f.name);
const dataViewFields: string[] = allFields.filter((f) => {
if (dataView.metaFields.includes(f)) {
return false;
}
@ -74,47 +99,22 @@ export const getFieldsFromKibanaIndexPattern = (indexPattern: DataView): string[
return true;
});
return indexPatternFields;
return dataViewFields;
};
/**
* Return a map of runtime_mappings for each of the index pattern field provided
* to provide in ES search queries
* @param indexPattern
* @param RuntimeMappings
* Record of ES field types.
*/
export function getCombinedRuntimeMappings(
indexPattern: DataView | undefined,
runtimeMappings?: RuntimeMappings
): RuntimeMappings | undefined {
let combinedRuntimeMappings = {};
// Add runtime field mappings defined by index pattern
if (indexPattern) {
const computedFields = indexPattern?.getComputedFields();
if (computedFields?.runtimeFields !== undefined) {
const indexPatternRuntimeMappings = computedFields.runtimeFields;
if (isRuntimeMappings(indexPatternRuntimeMappings)) {
combinedRuntimeMappings = { ...combinedRuntimeMappings, ...indexPatternRuntimeMappings };
}
}
}
// Use runtime field mappings defined inline from API
// and override fields with same name from index pattern
if (isRuntimeMappings(runtimeMappings)) {
combinedRuntimeMappings = { ...combinedRuntimeMappings, ...runtimeMappings };
}
if (isRuntimeMappings(combinedRuntimeMappings)) {
return combinedRuntimeMappings;
}
}
export interface FieldTypes {
[key: string]: ES_FIELD_TYPES;
}
/**
* Creates an array of objects representing the data grid schemas for each field.
* @param {FieldTypes} fieldTypes - The field types object.
* @param {string} resultsField - The results field.
* @returns {Array<{ id: string, schema: string | undefined, isSortable: boolean }>} - The array of data grid schemas.
*/
export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, resultsField: string) => {
return Object.keys(fieldTypes).map((field) => {
// Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
@ -166,6 +166,11 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results
export const NON_AGGREGATABLE = 'non-aggregatable';
/**
* Creates a data grid schema from an ES field type.
* @param {ES_FIELD_TYPES | undefined | estypes.MappingRuntimeField['type'] | 'number'} fieldType - The ES field type.
* @returns {string | undefined} - The data grid schema corresponding to the field type.
*/
export const getDataGridSchemaFromESFieldType = (
fieldType: ES_FIELD_TYPES | undefined | estypes.MappingRuntimeField['type'] | 'number'
): string | undefined => {
@ -206,6 +211,11 @@ export const getDataGridSchemaFromESFieldType = (
return schema;
};
/**
* Retrieves the data grid schema from the Kibana field type.
* @param {(DataViewField | undefined)} field - The Kibana field object.
* @returns {(string | undefined)} - The data grid schema corresponding to the field type.
*/
export const getDataGridSchemaFromKibanaFieldType = (
field: DataViewField | undefined
): string | undefined => {
@ -258,10 +268,11 @@ const getClassName = (className: string, isClassTypeBoolean: boolean) => {
*
* @param row - EUI data grid data row
* @param mlResultsField - Data frame analytics results field
* @param isClassTypeBoolean - Flag if the class type is boolean
* @returns nested object structure of feature importance values
*/
export const getFeatureImportance = (
row: Record<string, any>,
row: DataGridItem,
mlResultsField: string,
isClassTypeBoolean = false
): FeatureImportance[] => {
@ -294,7 +305,7 @@ export const getFeatureImportance = (
* @param mlResultsField - Data frame analytics results field
* @returns nested object structure of feature importance values
*/
export const getTopClasses = (row: Record<string, any>, mlResultsField: string): TopClasses => {
export const getTopClasses = (row: DataGridItem, mlResultsField: string): TopClasses => {
const topClasses: Array<{
class_name: FeatureImportanceClassName[];
class_probability: number[];
@ -305,15 +316,25 @@ export const getTopClasses = (row: Record<string, any>, mlResultsField: string):
return topClasses.map((tc) => getProcessedFields(tc)) as TopClasses;
};
/**
* Custom hook for rendering cell values in the data grid.
*
* @param {(DataView | undefined)} dataView - The data view.
* @param {IndexPagination} pagination - The pagination settings.
* @param {DataGridItem[]} tableItems - The table items.
* @param {?string} [resultsField] - The results field.
* @param {Function} cellPropsCallback - The callback function for setting cell properties.
* @returns {RenderCellValue} - The render cell value function.
*/
export const useRenderCellValue = (
indexPattern: DataView | undefined,
dataView: DataView | undefined,
pagination: IndexPagination,
tableItems: DataGridItem[],
resultsField?: string,
cellPropsCallback?: (
columnId: string,
cellValue: any,
fullItem: Record<string, any>,
fullItem: DataGridItem,
setCellProps: EuiDataGridCellValueElementProps['setCellProps']
) => void
): RenderCellValue => {
@ -335,14 +356,15 @@ export const useRenderCellValue = (
return null;
}
if (indexPattern === undefined) {
if (dataView === undefined) {
return null;
}
let format: ReturnType<typeof mlFieldFormatService.getFieldFormatFromIndexPattern>;
const field = dataView.fields.getByName(columnId);
let fieldFormat: FieldFormat | undefined;
if (indexPattern !== undefined) {
format = mlFieldFormatService.getFieldFormatFromIndexPattern(indexPattern, columnId, '');
if (field !== undefined) {
fieldFormat = dataView.getFormatterForField(field);
}
function getCellValue(cId: string) {
@ -384,15 +406,14 @@ export const useRenderCellValue = (
return null;
}
if (format !== undefined) {
return format.convert(cellValue, 'text');
if (fieldFormat !== undefined) {
return fieldFormat.convert(cellValue, 'text');
}
if (typeof cellValue === 'string' || cellValue === null) {
return cellValue;
}
const field = indexPattern.fields.getByName(columnId);
if (field?.type === KBN_FIELD_TYPES.DATE) {
return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000);
}
@ -404,17 +425,37 @@ export const useRenderCellValue = (
return cellValue;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [indexPattern?.fields, pagination.pageIndex, pagination.pageSize, tableItems]);
}, [dataView?.fields, pagination.pageIndex, pagination.pageSize, tableItems]);
return renderCellValue;
};
// Value can be nested or the fieldName itself might contain other special characters like `.`
export const getNestedOrEscapedVal = (obj: any, sortId: string) =>
getNestedProperty(obj, sortId, null) ?? obj[sortId];
/**
* Value can be nested or the fieldName itself might contain other special characters like `.`
* @param {unknown} obj - The object to get the nested property from.
* @param {string} sortId - The sort id attribute.
* @returns {*}
*/
export const getNestedOrEscapedVal = (obj: unknown, sortId: string) => {
if (isPopulatedObject(obj, [sortId])) {
return getNestedProperty(obj, sortId, null) ?? obj[sortId];
}
};
/**
* Interface definition for multi column sorter
*/
export interface MultiColumnSorter {
/**
* The id.
*/
id: string;
/**
* The direction.
*/
direction: 'asc' | 'desc';
/**
* The type of the sorter.
*/
type: string;
}
@ -466,23 +507,33 @@ export const multiColumnSortFactory = (sortingColumns: MultiColumnSorter[]) => {
return sortFn;
};
/**
* Displays an error toast message for the data grid column chart.
*
* @param {unknown} e - The error object or message.
* @param {CoreSetup['notifications']['toasts']} toastNotifications - The toast notifications service.
*/
export const showDataGridColumnChartErrorMessageToast = (
e: any,
e: unknown,
toastNotifications: CoreSetup['notifications']['toasts']
) => {
const error = extractErrorMessage(e);
toastNotifications.addDanger(
i18n.translate('xpack.ml.dataGrid.columnChart.ErrorMessageToast', {
defaultMessage: 'An error occurred fetching the histogram charts data: {error}',
values: { error: error !== '' ? error : e },
values: { error: extractErrorMessage(e as ErrorType) },
})
);
};
// helper function to transform { [key]: [val] } => { [key]: val }
// for when `fields` is used in es.search since response is always an array of values
// since response always returns an array of values for each field
/**
* Helper function to transform { [key]: [val] } => { [key]: val }
* for when `fields` is used in es.search since response is always an array of values
* since response always returns an array of values for each field
*
* @param {object} originalObj - The original object to get the processed fields from.
* @param {?(key: string) => boolean} [omitBy] - Optional callback.
* @returns {boolean) => { [key: string]: any; }}
*/
export const getProcessedFields = (originalObj: object, omitBy?: (key: string) => boolean) => {
const obj: { [key: string]: any } = { ...originalObj };
for (const key of Object.keys(obj)) {

View file

@ -0,0 +1,148 @@
/*
* 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 { isPopulatedObject } from '@kbn/ml-is-populated-object';
/**
* Represents a numeric data item.
*/
export interface NumericDataItem {
/**
* Numeric key.
*/
key: number;
/**
* Optional string or numeric key.
*/
key_as_string?: string | number;
/**
* Number of documents.
*/
doc_count: number;
}
/**
* Represents numeric chart data.
*/
export interface NumericChartData {
/**
* Array of numeric data items.
*/
data: NumericDataItem[];
/**
* Identifier of the chart.
*/
id: string;
/**
* Interval value.
*/
interval: number;
/**
* Statistics values.
*/
stats: [number, number];
/**
* Type of the chart data (numeric).
*/
type: 'numeric';
}
/**
* Determines if the provided argument is of type NumericChartData.
* @param {unknown} arg - The argument to check.
* @returns {arg is NumericChartData}
*/
export const isNumericChartData = (arg: unknown): arg is NumericChartData => {
return (
isPopulatedObject(arg, ['data', 'id', 'interval', 'stats', 'type']) && arg.type === 'numeric'
);
};
/**
* Represents an ordinal data item.
*/
export interface OrdinalDataItem {
/**
* Key.
*/
key: string;
/**
* Optional key as string.
*/
key_as_string?: string;
/**
* Number of documents.
*/
doc_count: number;
}
/**
* Represents ordinal chart data.
*/
export interface OrdinalChartData {
/**
* Cardinality value.
*/
cardinality: number;
/**
* Array of ordinal data items.
*/
data: OrdinalDataItem[];
/**
* Identifier of the chart.
*/
id: string;
/**
* Type of the chart data (ordinal or boolean).
*/
type: 'ordinal' | 'boolean';
}
/**
* Determines if the provided argument is of type OrdinalChartData.
* @param {unknown} arg - The argument to check.
* @returns {arg is OrdinalChartData}
*/
export const isOrdinalChartData = (arg: unknown): arg is OrdinalChartData => {
return (
isPopulatedObject(arg, ['data', 'cardinality', 'id', 'type']) &&
(arg.type === 'ordinal' || arg.type === 'boolean')
);
};
/**
* Represents unsupported chart data.
*/
export interface UnsupportedChartData {
/**
* Identifier of the chart.
*/
id: string;
/**
* Type of the chart data (unsupported).
*/
type: 'unsupported';
}
/**
* Determines if the provided argument is of type UnsupportedChartData.
* @param {unknown} arg - The argument to check.
* @returns {arg is UnsupportedChartData}
*/
export const isUnsupportedChartData = (arg: unknown): arg is UnsupportedChartData => {
return isPopulatedObject(arg, ['type']) && arg.type === 'unsupported';
};
/**
* Represents a chart data item that can be either numeric or ordinal.
*/
export type ChartDataItem = NumericDataItem | OrdinalDataItem;
/**
* Represents chart data that can be either numeric, ordinal, or unsupported.
*/
export type ChartData = NumericChartData | OrdinalChartData | UnsupportedChartData;

View file

@ -0,0 +1,302 @@
/*
* 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 { Dispatch, SetStateAction } from 'react';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
EuiDataGridCellValueElementProps,
EuiDataGridPaginationProps,
EuiDataGridSorting,
EuiDataGridColumn,
} from '@elastic/eui';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import { FeatureImportanceBaseline } from '@kbn/ml-data-frame-analytics-utils';
import { INDEX_STATUS } from './common';
import { ChartData } from './field_histograms';
interface Dictionary<TValue> {
[id: string]: TValue;
}
/**
* Type definition for a column id.
*/
export type ColumnId = string;
/**
* Type definition for a data grid item.
*/
export type DataGridItem = Record<string, any>;
/**
* Charts visibility state. `undefined` is used to indicate a non-initialized state.
*/
export type ChartsVisible = boolean | undefined;
/**
* Row count relation for the data grid's number of rows.
* It's an alias of `estypes.SearchTotalHitsRelation` ("eq" or "gte")
* that can also be undefined.
*/
export type RowCountRelation = estypes.SearchTotalHitsRelation | undefined;
/**
* Information about the row count.
*/
export interface RowCountInfo {
/**
* Row count.
*/
rowCount: number;
/**
* Row count relation.
*/
rowCountRelation: RowCountRelation;
}
/**
* Type representing the pagination settings for an index.
*/
export type IndexPagination = Pick<EuiDataGridPaginationProps, 'pageIndex' | 'pageSize'>;
/**
* Type for callback function for changing items per page.
* @param {*} pageSize - The page size.
*/
export type OnChangeItemsPerPage = (pageSize: number) => void;
/**
* Type for callback function for changing the current page.
* @param {*} pageIndex - The page index.
*/
export type OnChangePage = (pageIndex: number) => void;
/**
* Array of sortings specs.
*/
type SortSettings = Array<{
id: string;
direction: 'asc' | 'desc';
}>;
/**
* Type for callback function for sorting.
* @param {SortSettings} sortSettings - The sorting.
*/
export type OnSort = (sortSettings: SortSettings) => void;
/**
* Interface representing the cell value options used for rendering a cell in a data grid.
*/
interface CellValue {
/**
* The row index of the cell.
*/
rowIndex: number;
/**
* The column id of the cell.
*/
columnId: string;
/**
* Callback to set cell props
*/
setCellProps: EuiDataGridCellValueElementProps['setCellProps'];
}
/**
* Function used to render the cell value in a data grid.
* @param {CellValue} options - The options for rendering the cell value.
*/
export type RenderCellValue = (options: CellValue) => any;
/**
* Type representing ES sorting configuration.
*/
export type EsSorting = Dictionary<{
order: 'asc' | 'desc';
}>;
/**
* Return type of the `useIndexData` custom hook.
*/
export interface UseIndexDataReturnType
extends Pick<
UseDataGridReturnType,
| 'chartsVisible'
| 'chartsButtonVisible'
| 'ccsWarning'
| 'columnsWithCharts'
| 'errorMessage'
| 'invalidSortingColumnns'
| 'noDataMessage'
| 'onChangeItemsPerPage'
| 'onChangePage'
| 'onSort'
| 'pagination'
| 'setPagination'
| 'setVisibleColumns'
| 'rowCount'
| 'rowCountRelation'
| 'sortingColumns'
| 'status'
| 'tableItems'
| 'toggleChartVisibility'
| 'visibleColumns'
| 'baseline'
| 'predictionFieldName'
| 'resultsField'
> {
/**
* Callback to render cell values.
*/
renderCellValue: RenderCellValue;
/**
* Optional index pattern fields.
*/
indexPatternFields?: string[];
/**
* Optional time range.
*/
timeRangeMs?: TimeRangeMs;
}
/**
* Return type of the `useDataGrid` custom hook.
*/
export interface UseDataGridReturnType {
/**
* Boolean flag for CCS warning.
*/
ccsWarning: boolean;
/**
* Boolean flag for charts visibility.
*/
chartsVisible: ChartsVisible;
/**
* Boolean flag for charts button visibily.
*/
chartsButtonVisible: boolean;
/**
* Array of columns with charts data.
*/
columnsWithCharts: EuiDataGridColumn[];
/**
* Error message.
*/
errorMessage: string;
/**
* Array of invalid sorting columns.
*/
invalidSortingColumnns: ColumnId[];
/**
* No data message.
*/
noDataMessage: string;
/**
* Callback function for changing the number of items per page.
*/
onChangeItemsPerPage: OnChangeItemsPerPage;
/**
* Callback function for handling change of current page.
*/
onChangePage: OnChangePage;
/**
* Callback function for handling sort.
*/
onSort: OnSort;
/**
* Index pagination information.
*/
pagination: IndexPagination;
/**
* Function to reset pagination.
*/
resetPagination: () => void;
/**
* Row count.
*/
rowCount: number;
/**
* Row count relation.
*/
rowCountRelation: RowCountRelation;
/**
* Setter function for the CCS warning flag.
*/
setCcsWarning: Dispatch<SetStateAction<boolean>>;
/**
* Setter function for the column charts.
*/
setColumnCharts: Dispatch<SetStateAction<ChartData[]>>;
/**
* Setter function for the error message.
*/
setErrorMessage: Dispatch<SetStateAction<string>>;
/**
* Setter function for the no data message.
*/
setNoDataMessage: Dispatch<SetStateAction<string>>;
/**
* Setter function for pagination.
*/
setPagination: Dispatch<SetStateAction<IndexPagination>>;
/**
* Setter function for the row count info.
*/
setRowCountInfo: Dispatch<SetStateAction<RowCountInfo>>;
/**
* Setter function for the sorting columns.
*/
setSortingColumns: Dispatch<SetStateAction<EuiDataGridSorting['columns']>>;
/**
* Setter function for the index status.
*/
setStatus: Dispatch<SetStateAction<INDEX_STATUS>>;
/**
* Setter function for the table items.
*/
setTableItems: Dispatch<SetStateAction<DataGridItem[]>>;
/**
* Setter function for the visible columns.
*/
setVisibleColumns: Dispatch<SetStateAction<ColumnId[]>>;
/**
* Sorting columns.
*/
sortingColumns: EuiDataGridSorting['columns'];
/**
* Index status.
*/
status: INDEX_STATUS;
/**
* Table items.
*/
tableItems: DataGridItem[];
/**
* Function to toggle chart visibility.
*/
toggleChartVisibility: () => void;
/**
* Array of visible columns.
*/
visibleColumns: ColumnId[];
/**
* Optional feature importance baseline.
*/
baseline?: FeatureImportanceBaseline;
/**
* Optional prediction field name.
*/
predictionFieldName?: string;
/**
* Optional results field.
*/
resultsField?: string;
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/ml-data-grid",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,38 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react",
"@emotion/react/types/css-prop",
"@testing-library/jest-dom",
"@testing-library/react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/i18n",
"@kbn/core",
"@kbn/data-views-plugin",
"@kbn/field-types",
"@kbn/ml-nested-property",
"@kbn/ml-agg-utils",
"@kbn/ml-error-utils",
"@kbn/ml-data-frame-analytics-utils",
"@kbn/ui-theme",
"@kbn/i18n-react",
"@kbn/ml-is-populated-object",
"@kbn/ml-date-picker",
"@kbn/field-formats-plugin",
"@kbn/ml-query-utils",
"@kbn/ml-date-utils",
]
}

View file

@ -0,0 +1,3 @@
# @kbn/ml-date-utils
This package is used by the ML and Transform plugins and provides a set of utilities for working with dates.

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
export {
formatHumanReadableDate,
formatHumanReadableDateTime,
formatHumanReadableDateTimeSeconds,
timeFormatter,
validateTimeRange,
} from './src/date_utils';
export { TIME_FORMAT } from './src/time_format';

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/ml/date_utils'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-date-utils",
"owner": "@elastic/ml-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/ml-date-utils",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,85 @@
/*
* 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.
*/
// utility functions for handling dates
import dateMath from '@kbn/datemath';
import { formatDate } from '@elastic/eui';
import type { TimeRange } from '@kbn/es-query';
import { TIME_FORMAT } from './time_format';
/**
* Format a timestamp as human readable date.
*
* @param {number} ts - The timestamp to be formatted.
* @returns {string}
*/
export function formatHumanReadableDate(ts: number): string {
return formatDate(ts, 'MMMM Do YYYY');
}
/**
* Format a timestamp as human readable date including hours and minutes.
*
* @param {number} ts - The timestamp to be formatted.
* @returns {string}
*/
export function formatHumanReadableDateTime(ts: number): string {
return formatDate(ts, 'MMMM Do YYYY, HH:mm');
}
/**
* Format a timestamp as human readable date including hours, minutes and seconds.
*
* @param {number} ts - The timestamp to be formatted.
* @returns {string}
*/
export function formatHumanReadableDateTimeSeconds(ts: number): string {
return formatDate(ts, 'MMMM Do YYYY, HH:mm:ss');
}
/**
* Validate a time range of two string based dates.
* Copy of `src/plugins/data/public/query/timefilter/lib/validate_timerange.ts`
* for the time being so it can be used in packages.
*
* @param {?TimeRange} [time] - The time range to be validated.
* @returns {boolean}
*/
export function validateTimeRange(time?: TimeRange): boolean {
if (!time) return false;
const momentDateFrom = dateMath.parse(time.from);
const momentDateTo = dateMath.parse(time.to);
return !!(momentDateFrom && momentDateFrom.isValid() && momentDateTo && momentDateTo.isValid());
}
/**
* Transform a string based time range into one based on timestamps.
*
* @param {TimeRange} time - The time range to be transformed.
* @returns {{ to: any; from: any; }}
*/
export function createAbsoluteTimeRange(time: TimeRange) {
if (validateTimeRange(time) === false) {
return null;
}
return {
to: dateMath.parse(time.to)?.valueOf(),
from: dateMath.parse(time.from)?.valueOf(),
};
}
/**
* Format a timestamp into a human readable date based on the `TIME_FORMAT` spec.
*
* @param {number} value - The timestamp to be formatted.
* @returns {string}
*/
export const timeFormatter = (value: number): string => {
return formatDate(value, TIME_FORMAT);
};

View file

@ -5,4 +5,7 @@
* 2.0.
*/
/**
* Time format spec for string parsing.
*/
export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';

View file

@ -0,0 +1,22 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/datemath",
"@kbn/es-query",
]
}

View file

@ -3,4 +3,4 @@
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}
}

View file

@ -7,4 +7,5 @@
export { addExcludeFrozenToQuery } from './src/add_exclude_frozen_to_query';
export { buildBaseFilterCriteria } from './src/build_base_filter_criteria';
export { ES_CLIENT_TOTAL_HITS_RELATION } from './src/es_client_total_hits_relation';
export { getSafeAggregationName } from './src/get_safe_aggregation_name';

View file

@ -6,14 +6,10 @@
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
export function isMultiBucketAggregate(
arg: unknown
): arg is estypes.AggregationsMultiBucketAggregateBase {
return isPopulatedObject(arg, ['buckets']);
}
/**
* Custom enum for total hits relation values
*/
export const ES_CLIENT_TOTAL_HITS_RELATION: Record<
Uppercase<estypes.SearchTotalHitsRelation>,
estypes.SearchTotalHitsRelation
@ -21,5 +17,3 @@ export const ES_CLIENT_TOTAL_HITS_RELATION: Record<
EQ: 'eq',
GTE: 'gte',
} as const;
export type InfluencersFilterQuery = estypes.QueryDslQueryContainer;

View file

@ -0,0 +1,3 @@
# @kbn/ml-runtime-field-utils
This package is used by the ML and Transform plugins. It provides some utilities, mainly type guards, for working with runtime fields.

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export { getCombinedRuntimeMappings } from './src/get_combined_runtime_mappings';
export { isRuntimeField } from './src/is_runtime_field';
export { isRuntimeMappings, type RuntimeMappings } from './src/is_runtime_mappings';

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/ml/runtime_field_utils'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-runtime-field-utils",
"owner": "@elastic/ml-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/ml-runtime-field-utils",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,43 @@
/*
* 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 { DataView } from '@kbn/data-views-plugin/common';
import { isRuntimeMappings, type RuntimeMappings } from './is_runtime_mappings';
/**
* Return a map of runtime_mappings for each of the data view field provided
* to provide in ES search queries
* @param {DataView | undefined} dataView - The Kibana data view.
* @param runtimeMappings - Optional runtime mappings.
*/
export function getCombinedRuntimeMappings(
dataView: DataView | undefined,
runtimeMappings?: RuntimeMappings
): RuntimeMappings | undefined {
let combinedRuntimeMappings = {};
// Add runtime field mappings defined by index pattern
if (dataView) {
const computedFields = dataView?.getComputedFields();
if (computedFields?.runtimeFields !== undefined) {
const dataViewRuntimeMappings = computedFields.runtimeFields;
if (isRuntimeMappings(dataViewRuntimeMappings)) {
combinedRuntimeMappings = { ...combinedRuntimeMappings, ...dataViewRuntimeMappings };
}
}
}
// Use runtime field mappings defined inline from API
// and override fields with same name from index pattern
if (isRuntimeMappings(runtimeMappings)) {
combinedRuntimeMappings = { ...combinedRuntimeMappings, ...runtimeMappings };
}
if (isRuntimeMappings(combinedRuntimeMappings)) {
return combinedRuntimeMappings;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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 { isRuntimeField } from './is_runtime_field';
describe('isRuntimeField()', () => {
it('does not allow numbers', () => {
expect(isRuntimeField(1)).toBe(false);
});
it('does not allow null', () => {
expect(isRuntimeField(null)).toBe(false);
});
it('does not allow arrays', () => {
expect(isRuntimeField([])).toBe(false);
});
it('does not allow empty objects', () => {
expect(isRuntimeField({})).toBe(false);
});
it('does not allow objects with non-matching attributes', () => {
expect(isRuntimeField({ someAttribute: 'someValue' })).toBe(false);
expect(isRuntimeField({ type: 'wrong-type' })).toBe(false);
expect(isRuntimeField({ type: 'keyword', someAttribute: 'some value' })).toBe(false);
});
it('allows objects with type attribute only', () => {
expect(isRuntimeField({ type: 'keyword' })).toBe(true);
});
it('allows objects with both type and script attributes', () => {
expect(isRuntimeField({ type: 'keyword', script: 'some script', format: 'some format' })).toBe(
true
);
});
});

View file

@ -4,12 +4,19 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { RUNTIME_FIELD_TYPES } from '@kbn/data-plugin/common';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
type RuntimeType = typeof RUNTIME_FIELD_TYPES[number];
/**
* Type guard for a runtime field
*
* @param {unknown} arg - The item to be checked
* @returns {arg is estypes.MappingRuntimeField}
*/
export function isRuntimeField(arg: unknown): arg is estypes.MappingRuntimeField {
return (
((isPopulatedObject(arg, ['type']) && Object.keys(arg).length === 1) ||
@ -23,7 +30,3 @@ export function isRuntimeField(arg: unknown): arg is estypes.MappingRuntimeField
RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType)
);
}
export function isRuntimeMappings(arg: unknown): arg is estypes.MappingRuntimeFields {
return isPopulatedObject(arg) && Object.values(arg).every((d) => isRuntimeField(d));
}

View file

@ -0,0 +1,109 @@
/*
* 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 { isRuntimeMappings } from './is_runtime_mappings';
describe('isRuntimeMappings()', () => {
it('does not allow numbers', () => {
expect(isRuntimeMappings(1)).toBe(false);
});
it('does not allow null', () => {
expect(isRuntimeMappings(null)).toBe(false);
});
it('does not allow arrays', () => {
expect(isRuntimeMappings([])).toBe(false);
});
it('does not allow empty objects', () => {
expect(isRuntimeMappings({})).toBe(false);
});
it('does not allow objects with non-object inner structure', () => {
expect(isRuntimeMappings({ someAttribute: 'someValue' })).toBe(false);
});
it('does not allow objects with objects with unsupported inner structure', () => {
expect(isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: 'someValue' })).toBe(
false
);
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', someAttribute: 'some value' },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: 1234 },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { someAttribute: 'some value' } },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 1234 } },
})
).toBe(false);
});
it('allows object with most basic runtime field', () => {
expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true);
});
it('allows object with multiple most basic runtime fields', () => {
expect(
isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } })
).toBe(true);
});
it('allows object with runtime fields including scripts', () => {
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', script: 'some script as script' },
fieldName3: {
type: 'keyword',
script: {
source: 'source script',
},
},
fieldName4: {
type: 'keyword',
script: {
source: 'source script',
params: {},
},
},
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 'some script as source' } },
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: {
type: 'long',
script: {
source: 'source script',
params: {},
lang: 'lang',
},
},
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: {
type: 'long',
script: {
id: 'a script id',
},
},
})
).toBe(true);
});
});

View file

@ -0,0 +1,25 @@
/*
* 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { isRuntimeField } from './is_runtime_field';
/**
* Type guard for runtime mappings
*
* @param {unknown} arg - The item to be checked
* @returns {arg is RuntimeMappings}
*/
export function isRuntimeMappings(arg: unknown): arg is RuntimeMappings {
return isPopulatedObject(arg) && Object.values(arg).every((d) => isRuntimeField(d));
}
/**
* Alias for `estypes.MappingRuntimeFields`.
*/
export type RuntimeMappings = estypes.MappingRuntimeFields;

View file

@ -0,0 +1,23 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/data-views-plugin",
"@kbn/data-plugin",
"@kbn/ml-is-populated-object",
]
}

View file

@ -7,7 +7,7 @@
import React, { FC, useMemo } from 'react';
import type { EuiDataGridColumn } from '@elastic/eui/src/components/datagrid/data_grid_types';
import type { OrdinalChartData } from './field_histograms';
import type { OrdinalChartData } from '@kbn/ml-data-grid';
import { getTFPercentage } from '../../utils';
import { ColumnChart } from './column_chart';
import type { FieldDataRowProps } from '../../types';

View file

@ -11,9 +11,9 @@ import classNames from 'classnames';
import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/charts';
import { EuiDataGridColumn } from '@elastic/eui';
import './column_chart.scss';
import { isUnsupportedChartData, type ChartData } from '@kbn/ml-data-grid';
import { isUnsupportedChartData, type ChartData } from './field_histograms';
import './column_chart.scss';
import { useColumnChart } from './use_column_chart';

View file

@ -1,68 +0,0 @@
/*
* 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.
*/
export interface NumericDataItem {
key: number;
key_as_string?: string | number;
doc_count: number;
}
export interface NumericChartData {
data: NumericDataItem[];
id: string;
interval: number;
stats: [number, number];
type: 'numeric';
}
export const isNumericChartData = (arg: any): arg is NumericChartData => {
return (
typeof arg === 'object' &&
arg.hasOwnProperty('data') &&
arg.hasOwnProperty('id') &&
arg.hasOwnProperty('interval') &&
arg.hasOwnProperty('stats') &&
arg.hasOwnProperty('type') &&
arg.type === 'numeric'
);
};
export interface OrdinalDataItem {
key: string;
key_as_string?: string;
doc_count: number;
}
export interface OrdinalChartData {
cardinality: number;
data: OrdinalDataItem[];
id: string;
type: 'ordinal' | 'boolean';
}
export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => {
return (
typeof arg === 'object' &&
arg.hasOwnProperty('data') &&
arg.hasOwnProperty('cardinality') &&
arg.hasOwnProperty('id') &&
arg.hasOwnProperty('type') &&
(arg.type === 'ordinal' || arg.type === 'boolean')
);
};
export interface UnsupportedChartData {
id: string;
type: 'unsupported';
}
export const isUnsupportedChartData = (arg: any): arg is UnsupportedChartData => {
return typeof arg === 'object' && arg.hasOwnProperty('type') && arg.type === 'unsupported';
};
export type ChartDataItem = NumericDataItem | OrdinalDataItem;
export type ChartData = NumericChartData | OrdinalChartData | UnsupportedChartData;

View file

@ -7,7 +7,7 @@
import React, { FC } from 'react';
import type { EuiDataGridColumn } from '@elastic/eui';
import type { ChartData, OrdinalDataItem } from './field_histograms';
import type { ChartData, OrdinalDataItem } from '@kbn/ml-data-grid';
import { ColumnChart } from './column_chart';
import type { FieldDataRowProps } from '../../types/field_data_row';

View file

@ -19,7 +19,7 @@ import {
NumericChartData,
OrdinalChartData,
UnsupportedChartData,
} from './field_histograms';
} from '@kbn/ml-data-grid';
import { getFieldType, getLegendText, getXScaleType, useColumnChart } from './use_column_chart';

View file

@ -22,7 +22,7 @@ import {
type ChartDataItem,
type NumericDataItem,
type OrdinalDataItem,
} from './field_histograms';
} from '@kbn/ml-data-grid';
const NON_AGGREGATABLE = 'non-aggregatable';

View file

@ -47,6 +47,9 @@
"@kbn/ml-number-utils",
"@kbn/ml-query-utils",
"@kbn/ml-url-state",
"@kbn/ml-data-grid",
"@kbn/ml-error-utils",
"@kbn/ml-kibana-theme",
"@kbn/react-field",
"@kbn/rison",
"@kbn/saved-search-plugin",
@ -58,8 +61,6 @@
"@kbn/unified-search-plugin",
"@kbn/usage-collection-plugin",
"@kbn/utility-types",
"@kbn/ml-error-utils",
"@kbn/ml-kibana-theme",
],
"exclude": [
"target/**/*",

View file

@ -5,11 +5,7 @@
* 2.0.
*/
export { ES_CLIENT_TOTAL_HITS_RELATION } from './types/es_client';
export type { ChartData } from './types/field_histograms';
export { composeValidators, patternValidator } from './util/validators';
export { isRuntimeMappings, isRuntimeField } from './util/runtime_field_utils';
export type { RuntimeMappings } from './types/fields';
export { getDefaultCapabilities as getDefaultMlCapabilities } from './types/capabilities';
export { DATAFEED_STATE, JOB_STATE } from './constants/states';
export type { MlSummaryJob, SummaryJobState } from './types/anomaly_detection_jobs';

View file

@ -1,68 +0,0 @@
/*
* 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.
*/
export interface NumericDataItem {
key: number;
key_as_string?: string | number;
doc_count: number;
}
export interface NumericChartData {
data: NumericDataItem[];
id: string;
interval: number;
stats: [number, number];
type: 'numeric';
}
export const isNumericChartData = (arg: any): arg is NumericChartData => {
return (
typeof arg === 'object' &&
arg.hasOwnProperty('data') &&
arg.hasOwnProperty('id') &&
arg.hasOwnProperty('interval') &&
arg.hasOwnProperty('stats') &&
arg.hasOwnProperty('type') &&
arg.type === 'numeric'
);
};
export interface OrdinalDataItem {
key: string;
key_as_string?: string;
doc_count: number;
}
export interface OrdinalChartData {
cardinality: number;
data: OrdinalDataItem[];
id: string;
type: 'ordinal' | 'boolean';
}
export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => {
return (
typeof arg === 'object' &&
arg.hasOwnProperty('data') &&
arg.hasOwnProperty('cardinality') &&
arg.hasOwnProperty('id') &&
arg.hasOwnProperty('type') &&
(arg.type === 'ordinal' || arg.type === 'boolean')
);
};
export interface UnsupportedChartData {
id: string;
type: 'unsupported';
}
export const isUnsupportedChartData = (arg: any): arg is UnsupportedChartData => {
return typeof arg === 'object' && arg.hasOwnProperty('type') && arg.type === 'unsupported';
};
export type ChartDataItem = NumericDataItem | OrdinalDataItem;
export type ChartData = NumericChartData | OrdinalChartData | UnsupportedChartData;

View file

@ -1,104 +0,0 @@
/*
* 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import {
ML_JOB_AGGREGATION,
KIBANA_AGGREGATION,
ES_AGGREGATION,
} from '../constants/aggregation_types';
import { MLCATEGORY } from '../constants/field_types';
export const EVENT_RATE_FIELD_ID = '__ml_event_rate_count__';
export const METRIC_AGG_TYPE = 'metrics';
export type FieldId = string;
export type AggId = ML_JOB_AGGREGATION;
export type SplitField = Field | null;
export interface Field {
id: FieldId;
name: string;
type: ES_FIELD_TYPES;
aggregatable: boolean;
counter: boolean;
aggIds?: AggId[];
aggs?: Aggregation[];
runtimeField?: estypes.MappingRuntimeField;
}
export interface Aggregation {
id: AggId;
title: string;
kibanaName: KIBANA_AGGREGATION | null;
dslName: ES_AGGREGATION | null;
type: typeof METRIC_AGG_TYPE;
mlModelPlotAgg: {
min: string;
max: string;
};
fieldIds?: FieldId[];
fields?: Field[];
}
export interface NewJobCaps {
fields: Field[];
aggs: Aggregation[];
}
export interface NewJobCapsResponse {
[indexPattern: string]: NewJobCaps;
}
export interface AggFieldPair {
agg: Aggregation;
field: Field;
by?: {
field: SplitField;
value: string | null;
};
over?: {
field: SplitField;
value: string | null;
};
partition?: {
field: SplitField;
value: string | null;
};
excludeFrequent?: string;
}
export interface AggFieldNamePair {
agg: string;
field: string;
by?: {
field: string | null;
value: string | null;
};
over?: {
field: string | null;
value: string | null;
};
partition?: {
field: string | null;
value: string | null;
};
excludeFrequent?: string;
}
export const mlCategory: Field = {
id: MLCATEGORY,
name: MLCATEGORY,
type: ES_FIELD_TYPES.KEYWORD,
aggregatable: false,
counter: false,
};
export type RollupFields = Record<FieldId, [Record<'agg', ES_AGGREGATION>]>;
export type RuntimeMappings = estypes.MappingRuntimeFields;

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import { ES_AGGREGATION } from '@kbn/ml-anomaly-utils';
import type { ErrorType } from '@kbn/ml-error-utils';
import { type RuntimeMappings } from '@kbn/ml-runtime-field-utils';
import { Job, JobStats, IndicesOptions } from './anomaly_detection_jobs';
import { RuntimeMappings } from './fields';
import { ES_AGGREGATION } from '../constants/aggregation_types';
export interface MlJobsResponse {
jobs: Job[];

View file

@ -8,11 +8,11 @@
import type { SerializableRecord } from '@kbn/utility-types';
import type { LocatorPublic } from '@kbn/share-plugin/public';
import type { RefreshInterval, TimeRange } from '@kbn/data-plugin/common/query';
import type { InfluencersFilterQuery } from '@kbn/ml-anomaly-utils';
import type { DataFrameAnalysisConfigType } from '@kbn/ml-data-frame-analytics-utils';
import type { JobId } from './anomaly_detection_jobs/job';
import type { SearchQueryLanguage } from '../constants/search';
import type { ListingPageUrlState } from './common';
import type { InfluencersFilterQuery } from './es_client';
import { ML_PAGES } from '../constants/locator';
type OptionalPageState = object | undefined;

View file

@ -8,9 +8,13 @@
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { LineAnnotationDatum, RectAnnotationDatum } from '@elastic/charts';
import type { ErrorType } from '@kbn/ml-error-utils';
import type { MlEntityField, MlRecordForInfluencer } from '@kbn/ml-anomaly-utils';
import {
type MlEntityField,
type MlRecordForInfluencer,
ES_AGGREGATION,
ML_JOB_AGGREGATION,
} from '@kbn/ml-anomaly-utils';
import type { Datafeed, JobId, ModelSnapshot } from './anomaly_detection_jobs';
import { ES_AGGREGATION, ML_JOB_AGGREGATION } from '../constants/aggregation_types';
export interface GetStoppedPartitionResult {
jobs: string[] | Record<string, string[]>;

View file

@ -1,47 +0,0 @@
/*
* 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.
*/
// utility functions for handling dates
import dateMath from '@kbn/datemath';
import { formatDate } from '@elastic/eui';
import type { TimeRange } from '@kbn/es-query';
import { TIME_FORMAT } from '../constants/time_format';
export function formatHumanReadableDate(ts: number) {
return formatDate(ts, 'MMMM Do YYYY');
}
export function formatHumanReadableDateTime(ts: number): string {
return formatDate(ts, 'MMMM Do YYYY, HH:mm');
}
export function formatHumanReadableDateTimeSeconds(ts: number) {
return formatDate(ts, 'MMMM Do YYYY, HH:mm:ss');
}
export function validateTimeRange(time?: TimeRange): boolean {
if (!time) return false;
const momentDateFrom = dateMath.parse(time.from);
const momentDateTo = dateMath.parse(time.to);
return !!(momentDateFrom && momentDateFrom.isValid() && momentDateTo && momentDateTo.isValid());
}
export function createAbsoluteTimeRange(time: TimeRange) {
if (validateTimeRange(time) === false) {
return null;
}
return {
to: dateMath.parse(time.to)?.valueOf(),
from: dateMath.parse(time.from)?.valueOf(),
};
}
export const timeFormatter = (value: number) => {
return formatDate(value, TIME_FORMAT);
};

View file

@ -7,14 +7,14 @@
import { ES_FIELD_TYPES } from '@kbn/field-types';
import {
Field,
Aggregation,
NewJobCaps,
METRIC_AGG_TYPE,
RollupFields,
type Field,
type Aggregation,
type NewJobCaps,
type RollupFields,
EVENT_RATE_FIELD_ID,
} from '../types/fields';
import { ML_JOB_AGGREGATION } from '../constants/aggregation_types';
METRIC_AGG_TYPE,
ML_JOB_AGGREGATION,
} from '@kbn/ml-anomaly-utils';
export const categoryFieldTypes = [
ES_FIELD_TYPES.TEXT,

View file

@ -17,7 +17,12 @@ import type { SerializableRecord } from '@kbn/utility-types';
import { FilterStateStore } from '@kbn/es-query';
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isDefined } from '@kbn/ml-is-defined';
import type { MlEntityField } from '@kbn/ml-anomaly-utils';
import {
type MlEntityField,
ES_AGGREGATION,
ML_JOB_AGGREGATION,
MLCATEGORY,
} from '@kbn/ml-anomaly-utils';
import { ALLOWED_DATA_UNITS, JOB_ID_MAX_LENGTH } from '../constants/validation';
import { parseInterval } from './parse_interval';
import { maxLengthValidator } from './validators';
@ -31,8 +36,6 @@ import type {
} from '../types/anomaly_detection_jobs';
import type { MlServerLimits } from '../types/ml_server_info';
import type { JobValidationMessage, JobValidationMessageId } from '../constants/messages';
import { ES_AGGREGATION, ML_JOB_AGGREGATION } from '../constants/aggregation_types';
import { MLCATEGORY } from '../constants/field_types';
import { getAggregations, getDatafeedAggregations } from './datafeed_utils';
import { findAggField } from './validation_utils';
import { getFirstKeyInObject } from './object_utils';

View file

@ -1,139 +0,0 @@
/*
* 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 { isRuntimeField, isRuntimeMappings } from './runtime_field_utils';
describe('ML runtime field utils', () => {
describe('isRuntimeField()', () => {
it('does not allow numbers', () => {
expect(isRuntimeField(1)).toBe(false);
});
it('does not allow null', () => {
expect(isRuntimeField(null)).toBe(false);
});
it('does not allow arrays', () => {
expect(isRuntimeField([])).toBe(false);
});
it('does not allow empty objects', () => {
expect(isRuntimeField({})).toBe(false);
});
it('does not allow objects with non-matching attributes', () => {
expect(isRuntimeField({ someAttribute: 'someValue' })).toBe(false);
expect(isRuntimeField({ type: 'wrong-type' })).toBe(false);
expect(isRuntimeField({ type: 'keyword', someAttribute: 'some value' })).toBe(false);
});
it('allows objects with type attribute only', () => {
expect(isRuntimeField({ type: 'keyword' })).toBe(true);
});
it('allows objects with both type and script attributes', () => {
expect(
isRuntimeField({ type: 'keyword', script: 'some script', format: 'some format' })
).toBe(true);
});
});
describe('isRuntimeMappings()', () => {
it('does not allow numbers', () => {
expect(isRuntimeMappings(1)).toBe(false);
});
it('does not allow null', () => {
expect(isRuntimeMappings(null)).toBe(false);
});
it('does not allow arrays', () => {
expect(isRuntimeMappings([])).toBe(false);
});
it('does not allow empty objects', () => {
expect(isRuntimeMappings({})).toBe(false);
});
it('does not allow objects with non-object inner structure', () => {
expect(isRuntimeMappings({ someAttribute: 'someValue' })).toBe(false);
});
it('does not allow objects with objects with unsupported inner structure', () => {
expect(isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: 'someValue' })).toBe(
false
);
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', someAttribute: 'some value' },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: 1234 },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { someAttribute: 'some value' } },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 1234 } },
})
).toBe(false);
});
it('allows object with most basic runtime field', () => {
expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true);
});
it('allows object with multiple most basic runtime fields', () => {
expect(
isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } })
).toBe(true);
});
it('allows object with runtime fields including scripts', () => {
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', script: 'some script as script' },
fieldName3: {
type: 'keyword',
script: {
source: 'source script',
},
},
fieldName4: {
type: 'keyword',
script: {
source: 'source script',
params: {},
},
},
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 'some script as source' } },
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: {
type: 'long',
script: {
source: 'source script',
params: {},
lang: 'lang',
},
},
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: {
type: 'long',
script: {
id: 'a script id',
},
},
})
).toBe(true);
});
});
});

View file

@ -15,8 +15,8 @@ import React from 'react';
import { EuiDescriptionList } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils';
import { Annotation } from '../../../../../common/types/annotations';
import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils';
interface Props {
annotation: Annotation;

View file

@ -48,7 +48,7 @@ import {
} from '../../../../../common/constants/annotations';
import { withKibana } from '@kbn/kibana-react-plugin/public';
import { ML_APP_LOCATOR, ML_PAGES } from '../../../../../common/constants/locator';
import { timeFormatter } from '../../../../../common/util/date_utils';
import { timeFormatter } from '@kbn/ml-date-utils';
import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context';
import { DatafeedChartFlyout } from '../../../jobs/jobs_list/components/datafeed_chart_flyout';
import { RevertModelSnapshotFlyout } from '../../model_snapshots/revert_model_snapshot_flyout';

View file

@ -12,13 +12,12 @@ import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ML_JOB_AGGREGATION, isRuleSupported, isMultiBucketAnomaly } from '@kbn/ml-anomaly-utils';
import {
formatHumanReadableDate,
formatHumanReadableDateTime,
formatHumanReadableDateTimeSeconds,
} from '../../../../common/util/date_utils';
import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types';
} from '@kbn/ml-date-utils';
import { DescriptionCell } from './description_cell';
import { DetectorCell } from './detector_cell';
@ -27,7 +26,6 @@ import { InfluencersCell } from './influencers_cell';
import { LinksMenu } from './links_menu';
import { checkPermission } from '../../capabilities/check_capabilities';
import { mlFieldFormatService } from '../../services/field_format_service';
import { isRuleSupported, isMultiBucketAnomaly } from '@kbn/ml-anomaly-utils';
import { formatValue } from '../../formatters/format_value';
import { INFLUENCERS_LIMIT, ANOMALIES_TABLE_TABS } from './anomalies_table_constants';
import { SeverityCell } from './severity_cell';

View file

@ -23,11 +23,12 @@ import {
getSeverityColor,
showActualForFunction,
showTypicalForFunction,
type MlAnomaliesTableRecord,
type MlAnomalyRecordDoc,
ML_JOB_AGGREGATION,
} from '@kbn/ml-anomaly-utils';
import type { MlAnomaliesTableRecord, MLAnomalyDoc } from '@kbn/ml-anomaly-utils';
import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types';
import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils';
import { EntityCell, EntityCellFilter } from '../entity_cell';
import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils';
import { formatValue } from '../../formatters/format_value';
import { useMlKibana } from '../../contexts/kibana';
@ -617,7 +618,7 @@ const RecordScore: FC<{ score: number }> = ({ score }) => {
);
};
function getAnomalyType(explanation: MLAnomalyDoc['anomaly_score_explanation']) {
function getAnomalyType(explanation: MlAnomalyRecordDoc['anomaly_score_explanation']) {
if (
explanation === undefined ||
explanation.anomaly_length === undefined ||

View file

@ -29,13 +29,10 @@ import {
type MlKibanaUrlConfig,
type MlAnomaliesTableRecord,
} from '@kbn/ml-anomaly-utils';
import { formatHumanReadableDateTimeSeconds, timeFormatter } from '@kbn/ml-date-utils';
import { mlJobService } from '../../services/job_service';
import { getDataViewIdFromName } from '../../util/index_utils';
import { getInitialAnomaliesLayers, getInitialSourceIndexFieldLayers } from '../../../maps/util';
import {
formatHumanReadableDateTimeSeconds,
timeFormatter,
} from '../../../../common/util/date_utils';
import { parseInterval } from '../../../../common/util/parse_interval';
import { ml } from '../../services/ml_api_service';
import { escapeKueryForFieldValuePair, replaceStringTokens } from '../../util/string_utils';

View file

@ -1,126 +0,0 @@
/*
* 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 { Dispatch, SetStateAction } from 'react';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
EuiDataGridCellValueElementProps,
EuiDataGridPaginationProps,
EuiDataGridSorting,
EuiDataGridColumn,
} from '@elastic/eui';
import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker';
import { FeatureImportanceBaseline, INDEX_STATUS } from '@kbn/ml-data-frame-analytics-utils';
import { Dictionary } from '../../../../common/types/common';
import { ChartData } from '../../../../common/types/field_histograms';
export type ColumnId = string;
export type DataGridItem = Record<string, any>;
// `undefined` is used to indicate a non-initialized state.
export type ChartsVisible = boolean | undefined;
export type RowCountRelation = estypes.SearchTotalHitsRelation | undefined;
export interface RowCountInfo {
rowCount: number;
rowCountRelation: RowCountRelation;
}
export type IndexPagination = Pick<EuiDataGridPaginationProps, 'pageIndex' | 'pageSize'>;
export type OnChangeItemsPerPage = (pageSize: any) => void;
export type OnChangePage = (pageIndex: any) => void;
export type OnSort = (
sc: Array<{
id: string;
direction: 'asc' | 'desc';
}>
) => void;
export type RenderCellValue = ({
rowIndex,
columnId,
setCellProps,
}: {
rowIndex: number;
columnId: string;
setCellProps: EuiDataGridCellValueElementProps['setCellProps'];
}) => any;
export type EsSorting = Dictionary<{
order: 'asc' | 'desc';
}>;
export interface UseIndexDataReturnType
extends Pick<
UseDataGridReturnType,
| 'chartsVisible'
| 'chartsButtonVisible'
| 'ccsWarning'
| 'columnsWithCharts'
| 'errorMessage'
| 'invalidSortingColumnns'
| 'noDataMessage'
| 'onChangeItemsPerPage'
| 'onChangePage'
| 'onSort'
| 'pagination'
| 'setPagination'
| 'setVisibleColumns'
| 'rowCount'
| 'rowCountRelation'
| 'sortingColumns'
| 'status'
| 'tableItems'
| 'toggleChartVisibility'
| 'visibleColumns'
| 'baseline'
| 'predictionFieldName'
| 'resultsField'
> {
renderCellValue: RenderCellValue;
indexPatternFields?: string[];
timeRangeMs?: TimeRangeMs;
}
export interface UseDataGridReturnType {
ccsWarning: boolean;
chartsVisible: ChartsVisible;
chartsButtonVisible: boolean;
columnsWithCharts: EuiDataGridColumn[];
errorMessage: string;
invalidSortingColumnns: ColumnId[];
noDataMessage: string;
onChangeItemsPerPage: OnChangeItemsPerPage;
onChangePage: OnChangePage;
onSort: OnSort;
pagination: IndexPagination;
resetPagination: () => void;
rowCount: number;
rowCountRelation: RowCountRelation;
setCcsWarning: Dispatch<SetStateAction<boolean>>;
setColumnCharts: Dispatch<SetStateAction<ChartData[]>>;
setErrorMessage: Dispatch<SetStateAction<string>>;
setNoDataMessage: Dispatch<SetStateAction<string>>;
setPagination: Dispatch<SetStateAction<IndexPagination>>;
setRowCountInfo: Dispatch<SetStateAction<RowCountInfo>>;
setSortingColumns: Dispatch<SetStateAction<EuiDataGridSorting['columns']>>;
setStatus: Dispatch<SetStateAction<INDEX_STATUS>>;
setTableItems: Dispatch<SetStateAction<DataGridItem[]>>;
setVisibleColumns: Dispatch<SetStateAction<ColumnId[]>>;
sortingColumns: EuiDataGridSorting['columns'];
status: INDEX_STATUS;
tableItems: DataGridItem[];
toggleChartVisibility: () => void;
visibleColumns: ColumnId[];
baseline?: FeatureImportanceBaseline;
predictionFieldName?: string;
resultsField?: string;
}

View file

@ -10,9 +10,8 @@ import React, { FC } from 'react';
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { ML_ENTITY_FIELD_OPERATIONS } from '@kbn/ml-anomaly-utils';
import { ML_ENTITY_FIELD_OPERATIONS, MLCATEGORY } from '@kbn/ml-anomaly-utils';
import { EMPTY_FIELD_VALUE_LABEL } from '../../timeseriesexplorer/components/entity_control/entity_control';
import { MLCATEGORY } from '../../../../common/constants/field_types';
import { blurButtonOnClick } from '../../util/component_utils';
export type EntityCellFilter = (

View file

@ -9,7 +9,7 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiHighlight, EuiToolTip } fr
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FieldIcon } from '@kbn/react-field';
import { EVENT_RATE_FIELD_ID, Field } from '../../../../common/types/fields';
import { EVENT_RATE_FIELD_ID, type Field } from '@kbn/ml-anomaly-utils';
import { getKbnFieldIconType } from '../../../../common/util/get_field_icon_types';
export type FieldForStats = Pick<Field, 'id' | 'type'>;

View file

@ -7,10 +7,10 @@
import React, { ReactNode, useCallback } from 'react';
import { EuiComboBoxOptionOption } from '@elastic/eui';
import type { Field } from '@kbn/ml-anomaly-utils';
import { optionCss } from './eui_combo_box_with_field_stats';
import { useFieldStatsFlyoutContext } from '.';
import { FieldForStats, FieldStatsInfoButton } from './field_stats_info_button';
import { Field } from '../../../../common/types/fields';
interface Option extends EuiComboBoxOptionOption<string> {
field: Field;

View file

@ -30,8 +30,8 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { type ErrorType, extractErrorProperties } from '@kbn/ml-error-utils';
import type { DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-utils';
import type { JobType } from '../../../../../common/types/saved_objects';
import { useMlApiContext, useMlKibana } from '../../../contexts/kibana';
import { CannotImportJobsCallout } from './cannot_import_jobs_callout';

View file

@ -6,6 +6,7 @@
*/
import type { DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-utils';
import type { JobType } from '../../../../../common/types/saved_objects';
import type { Job, Datafeed } from '../../../../../common/types/anomaly_detection_jobs';
import type { Filter } from '../../../../../common/types/filters';

View file

@ -18,12 +18,14 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { euiLightVars as theme } from '@kbn/ui-theme';
import { timeFormatter } from '@kbn/ml-date-utils';
import { JobMessage } from '../../../../common/types/audit_message';
import { JobIcon } from '../job_message_icon';
import { timeFormatter } from '../../../../common/util/date_utils';
import { blurButtonOnClick } from '../../util/component_utils';
import { JobIcon } from '../job_message_icon';
interface JobMessagesProps {
messages: JobMessage[];
loading: boolean;

View file

@ -16,6 +16,7 @@ import {
EuiBasicTableColumn,
} from '@elastic/eui';
import { timeFormatter } from '@kbn/ml-date-utils';
import { checkPermission } from '../../capabilities/check_capabilities';
import { EditModelSnapshotFlyout } from './edit_model_snapshot_flyout';
import { RevertModelSnapshotFlyout } from './revert_model_snapshot_flyout';
@ -26,7 +27,6 @@ import {
ModelSnapshot,
CombinedJobWithStats,
} from '../../../../common/types/anomaly_detection_jobs';
import { timeFormatter } from '../../../../common/util/date_utils';
interface Props {
job: CombinedJobWithStats;

View file

@ -6,8 +6,7 @@
*/
import React, { FC, useState, useCallback, useMemo, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiFlyout,
EuiFlyoutHeader,
@ -28,6 +27,10 @@ import {
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { timeFormatter } from '@kbn/ml-date-utils';
import {
ModelSnapshot,
CombinedJobWithStats,
@ -41,7 +44,7 @@ import { EventRateChart } from '../../../jobs/new_job/pages/components/charts/ev
import { Anomaly } from '../../../jobs/new_job/common/results_loader/results_loader';
import { parseInterval } from '../../../../../common/util/parse_interval';
import { CreateCalendar, CalendarEvent } from './create_calendar';
import { timeFormatter } from '../../../../../common/util/date_utils';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
interface Props {

View file

@ -28,10 +28,13 @@ import { Query } from '@kbn/data-plugin/common/query';
import { DataView } from '@kbn/data-views-plugin/public';
import { stringHash } from '@kbn/ml-string-hash';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import { isRuntimeMappings } from '../../../../common/util/runtime_field_utils';
import { RuntimeMappings } from '../../../../common/types/fields';
import {
getCombinedRuntimeMappings,
isRuntimeMappings,
type RuntimeMappings,
} from '@kbn/ml-runtime-field-utils';
import { getProcessedFields } from '@kbn/ml-data-grid';
import { getCombinedRuntimeMappings, getProcessedFields } from '../data_grid';
import { useCurrentThemeVars, useMlApiContext, useMlKibana } from '../../contexts/kibana';
// Separate imports for lazy loadable VegaChart and related code

View file

@ -7,9 +7,14 @@
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import { type DataFrameAnalyticsConfig, INDEX_STATUS } from '@kbn/ml-data-frame-analytics-utils';
import { type DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-utils';
import {
EsSorting,
UseDataGridReturnType,
getProcessedFields,
INDEX_STATUS,
} from '@kbn/ml-data-grid';
import { EsSorting, UseDataGridReturnType, getProcessedFields } from '../../components/data_grid';
import { ml } from '../../services/ml_api_service';
import { newJobCapsServiceAnalytics } from '../../services/new_job_capabilities/new_job_capabilities_service_analytics';

View file

@ -23,4 +23,6 @@ export { getIndexFields } from './get_index_fields';
export { getDestinationIndex } from './get_destination_index';
export { getScatterplotMatrixLegendType } from './get_scatterplot_matrix_legend_type';
export { renderCellPopoverFactory } from './render_cell_popover';
export { useResultsViewConfig } from './use_results_view_config';

View file

@ -0,0 +1,107 @@
/*
* 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 React from 'react';
import { EuiCodeBlock, EuiDataGridCellPopoverElementProps } from '@elastic/eui';
import {
type DataFrameAnalysisConfigType,
type FeatureImportanceBaseline,
type FeatureImportance,
type TopClasses,
ANALYSIS_CONFIG_TYPE,
DEFAULT_RESULTS_FIELD,
} from '@kbn/ml-data-frame-analytics-utils';
import {
getFeatureImportance,
getTopClasses,
type DataGridItem,
type IndexPagination,
} from '@kbn/ml-data-grid';
import { DecisionPathPopover } from '../pages/analytics_exploration/components/feature_importance/decision_path_popover';
interface RenderCellPopoverFactoryOptions {
analysisType?: DataFrameAnalysisConfigType | 'unknown';
baseline?: FeatureImportanceBaseline;
data: DataGridItem[];
pagination: IndexPagination;
predictionFieldName?: string;
resultsField?: string;
}
export const renderCellPopoverFactory =
({
analysisType,
baseline,
data,
pagination,
predictionFieldName,
resultsField,
}: RenderCellPopoverFactoryOptions) =>
(popoverProps: EuiDataGridCellPopoverElementProps) => {
const { schema, rowIndex, cellContentsElement, DefaultCellPopover } = popoverProps;
if (
analysisType === ANALYSIS_CONFIG_TYPE.REGRESSION ||
analysisType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION ||
analysisType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION
) {
if (schema === 'featureImportance') {
const row = data[rowIndex - pagination.pageIndex * pagination.pageSize];
if (!row) return <div />;
// if resultsField for some reason is not available then use ml
const mlResultsField = resultsField ?? DEFAULT_RESULTS_FIELD;
let predictedValue: string | number | undefined;
let predictedProbability: number | undefined;
let topClasses: TopClasses = [];
if (
predictionFieldName !== undefined &&
row &&
row[`${mlResultsField}.${predictionFieldName}`] !== undefined
) {
predictedValue = row[`${mlResultsField}.${predictionFieldName}`];
topClasses = getTopClasses(row, mlResultsField);
predictedProbability = row[`${mlResultsField}.prediction_probability`];
}
const isClassTypeBoolean = topClasses.reduce(
(p, c) => typeof c.class_name === 'boolean' || p,
false
);
const parsedFIArray: FeatureImportance[] = getFeatureImportance(
row,
mlResultsField,
isClassTypeBoolean
);
return (
<div data-test-subj="mlDFAFeatureImportancePopover">
<DecisionPathPopover
analysisType={analysisType}
predictedValue={predictedValue}
predictedProbability={predictedProbability}
baseline={baseline}
featureImportance={parsedFIArray}
topClasses={topClasses}
predictionFieldName={
predictionFieldName ? predictionFieldName.replace('_prediction', '') : undefined
}
/>
</div>
);
} else if (schema === 'featureInfluence') {
return <EuiCodeBlock isCopyable={true}>{cellContentsElement.textContent}</EuiCodeBlock>;
} else {
return <DefaultCellPopover {...popoverProps} />;
}
} else {
return <DefaultCellPopover {...popoverProps} />;
}
};

View file

@ -13,7 +13,7 @@ import { LEFT_ALIGNMENT, SortableProperties } from '@elastic/eui/lib/services';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { FieldSelectionItem } from '@kbn/ml-data-frame-analytics-utils';
import type { FieldSelectionItem } from '@kbn/ml-data-frame-analytics-utils';
import { useFieldStatsTrigger } from '../../../../../components/field_stats_flyout/use_field_stats_trigger';
import {
FieldForStats,

View file

@ -21,13 +21,19 @@ import { debounce, cloneDeep } from 'lodash';
import { Query } from '@kbn/data-plugin/common/query';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { FieldStatsServices } from '@kbn/unified-field-list-plugin/public';
import {
getCombinedRuntimeMappings,
isRuntimeMappings,
isRuntimeField,
type RuntimeMappings as RuntimeMappingsType,
} from '@kbn/ml-runtime-field-utils';
import {
type FieldSelectionItem,
ANALYSIS_CONFIG_TYPE,
TRAINING_PERCENT_MIN,
TRAINING_PERCENT_MAX,
} from '@kbn/ml-data-frame-analytics-utils';
import { DataGrid } from '@kbn/ml-data-grid';
import { useMlKibana } from '../../../../../contexts/kibana';
import {
EuiComboBoxWithFieldStats,
@ -36,14 +42,8 @@ import {
import { FieldForStats } from '../../../../../components/field_stats_flyout/field_stats_info_button';
import { newJobCapsServiceAnalytics } from '../../../../../services/new_job_capabilities/new_job_capabilities_service_analytics';
import { useMlContext } from '../../../../../contexts/ml';
import { getCombinedRuntimeMappings } from '../../../../../components/data_grid/common';
import { getScatterplotMatrixLegendType } from '../../../../common/get_scatterplot_matrix_legend_type';
import { RuntimeMappings as RuntimeMappingsType } from '../../../../../../../common/types/fields';
import {
isRuntimeMappings,
isRuntimeField,
} from '../../../../../../../common/util/runtime_field_utils';
import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state';
import { Messages } from '../shared';
import {
@ -58,7 +58,6 @@ import { ContinueButton } from '../continue_button';
import { JobType } from './job_type';
import { SupportedFieldsMessage } from './supported_fields_message';
import { AnalysisFieldsTable } from './analysis_fields_table';
import { DataGrid } from '../../../../../components/data_grid';
import { fetchExplainData } from '../shared';
import { useIndexData } from '../../hooks';
import { ExplorationQueryBar } from '../../../analytics_exploration/components/exploration_query_bar';

View file

@ -8,12 +8,12 @@
import { i18n } from '@kbn/i18n';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { EVENT_RATE_FIELD_ID } from '@kbn/ml-anomaly-utils';
import {
ANALYSIS_CONFIG_TYPE,
BASIC_NUMERICAL_TYPES,
EXTENDED_NUMERICAL_TYPES,
} from '@kbn/ml-data-frame-analytics-utils';
import { EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields';
import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state';
export const CATEGORICAL_TYPES = new Set(['ip', 'keyword']);

Some files were not shown because too many files have changed in this diff Show more