From aadbe327b052fe8ae284c12c5d42943e6205177b Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 25 Jan 2021 17:11:52 +0000 Subject: [PATCH 1/4] skip failing es promotion suite (#89181) (cherry picked from commit 342f9fbac15ce118ad088d2908c263d36ec23be4) --- .../feature_controls/remote_clusters_security.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts index b1edc74607161..e6b5a7ac77d7c 100644 --- a/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts +++ b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts @@ -13,7 +13,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const managementMenu = getService('managementMenu'); - describe('security', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/89181 + describe.skip('security', () => { before(async () => { await esArchiver.load('empty_kibana'); await PageObjects.common.navigateToApp('home'); From 5c9521079ed6a03e8c16dc306f7c9473bc54168e Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Wed, 27 Jan 2021 15:12:20 -0600 Subject: [PATCH 2/4] [7.x] [ML] Add embedded map to geo_point fields for Data Visualizer (#88880) (#89465) --- .../maps/public/embeddable/map_embeddable.tsx | 2 +- x-pack/plugins/ml/kibana.json | 6 +- .../plugins/ml/public/application/_index.scss | 1 + x-pack/plugins/ml/public/application/app.tsx | 3 + .../components/ml_embedded_map/_index.scss | 1 + .../ml_embedded_map/_ml_embedded_map.scss | 8 + .../components/ml_embedded_map/index.ts | 7 + .../ml_embedded_map/ml_embedded_map.tsx | 160 ++++++++++++++++++ .../contexts/kibana/kibana_context.ts | 4 + .../expanded_row/file_based_expanded_row.tsx | 2 +- .../geo_point_content/format_utils.ts | 76 +++++++++ .../geo_point_content/geo_point_content.tsx | 79 +++++++++ .../expanded_row/geo_point_content/index.ts | 7 + .../file_datavisualizer_view/_index.scss | 2 +- .../components/expanded_row/expanded_row.tsx | 28 ++- .../expanded_row/geo_point_content.tsx | 78 +++++++++ .../examples_list/examples_list.tsx | 34 ++-- .../field_data_row/top_values/top_values.tsx | 108 +++++++----- .../datavisualizer/index_based/page.tsx | 35 ++-- .../stats_table/_field_data_row.scss | 15 ++ .../datavisualizer/stats_table/_index.scss | 10 +- .../field_data_expanded_row/_index.scss | 6 + .../boolean_content.tsx | 7 +- .../field_data_expanded_row/date_content.tsx | 7 +- .../document_stats.tsx | 3 +- .../expanded_row_content.tsx | 24 +++ .../geo_point_content.tsx | 35 ---- .../field_data_expanded_row/index.ts | 2 +- .../field_data_expanded_row/ip_content.tsx | 20 +-- .../keyword_content.tsx | 19 +-- .../number_content.tsx | 26 +-- .../field_data_expanded_row/other_content.tsx | 6 +- .../field_data_expanded_row/text_content.tsx | 7 +- .../components/field_data_row/_index.scss | 3 + .../field_data_row/distinct_values.tsx | 2 +- .../field_data_row/document_stats.tsx | 2 +- .../field_data_row/number_content_preview.tsx | 7 +- .../application/util/dependency_cache.ts | 3 + x-pack/plugins/ml/public/plugin.ts | 8 +- .../models/data_visualizer/data_visualizer.ts | 21 +-- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../data_visualizer/file_data_visualizer.ts | 41 +++++ .../files_to_import/geo_file.csv | 14 ++ .../data_visualizer/index_data_visualizer.ts | 79 ++++++++- .../services/ml/data_visualizer_table.ts | 51 +++++- 46 files changed, 842 insertions(+), 223 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss create mode 100644 x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss create mode 100644 x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts create mode 100644 x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx delete mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx create mode 100644 x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss create mode 100644 x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 67981ab3aede5..bcdc23bddd2eb 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -68,7 +68,7 @@ import { MapEmbeddableInput, MapEmbeddableOutput, } from './types'; -export { MapEmbeddableInput }; +export { MapEmbeddableInput, MapEmbeddableOutput }; export class MapEmbeddable extends Embeddable diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 8ec9b8ee976d4..1c47512e0b3de 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -24,7 +24,8 @@ "security", "spaces", "management", - "licenseManagement" + "licenseManagement", + "maps" ], "server": true, "ui": true, @@ -35,7 +36,8 @@ "dashboard", "savedObjects", "home", - "spaces" + "spaces", + "maps" ], "extraPublicDirs": [ "common" diff --git a/x-pack/plugins/ml/public/application/_index.scss b/x-pack/plugins/ml/public/application/_index.scss index 7512c180970ad..da1c226a665f6 100644 --- a/x-pack/plugins/ml/public/application/_index.scss +++ b/x-pack/plugins/ml/public/application/_index.scss @@ -30,6 +30,7 @@ @import 'components/navigation_menu/index'; @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly @import 'components/stats_bar/index'; + @import 'components/ml_embedded_map/index'; // Hacks are last so they can overwrite anything above if needed @import 'hacks'; diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index d3a055f957c3a..68ee32e24391c 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -77,6 +77,8 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { security: deps.security, licenseManagement: deps.licenseManagement, storage: localStorage, + embeddable: deps.embeddable, + maps: deps.maps, ...coreStart, }; @@ -118,6 +120,7 @@ export const renderApp = ( http: coreStart.http, security: deps.security, urlGenerators: deps.share.urlGenerators, + maps: deps.maps, }); appMountParams.onAppLeave((actions) => actions.default()); diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss new file mode 100644 index 0000000000000..6d0d30dae670e --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss @@ -0,0 +1 @@ +@import 'ml_embedded_map'; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss new file mode 100644 index 0000000000000..495fc40ddb27c --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss @@ -0,0 +1,8 @@ +.mlEmbeddedMapContent { + width: 100%; + height: 100%; + display: flex; + flex: 1 1 100%; + z-index: 1; + min-height: 0; // Absolute must for Firefox to scroll contents +} diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts b/x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts new file mode 100644 index 0000000000000..ce5dfb5171a6d --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { MlEmbeddedMapComponent } from './ml_embedded_map'; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx new file mode 100644 index 0000000000000..d5fdc9d52a102 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useRef, useState } from 'react'; + +import { htmlIdGenerator } from '@elastic/eui'; +import { LayerDescriptor } from '../../../../../maps/common/descriptor_types'; +import { + MapEmbeddable, + MapEmbeddableInput, + MapEmbeddableOutput, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../maps/public/embeddable'; +import { MAP_SAVED_OBJECT_TYPE, RenderTooltipContentParams } from '../../../../../maps/public'; +import { + EmbeddableFactory, + ErrorEmbeddable, + isErrorEmbeddable, + ViewMode, +} from '../../../../../../../src/plugins/embeddable/public'; +import { useMlKibana } from '../../contexts/kibana'; + +export function MlEmbeddedMapComponent({ + layerList, + mapEmbeddableInput, + renderTooltipContent, +}: { + layerList: LayerDescriptor[]; + mapEmbeddableInput?: MapEmbeddableInput; + renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element; +}) { + const [embeddable, setEmbeddable] = useState(); + + const embeddableRoot: React.RefObject = useRef(null); + const baseLayers = useRef(); + + const { + services: { embeddable: embeddablePlugin, maps: mapsPlugin }, + } = useMlKibana(); + + const factory: + | EmbeddableFactory + | undefined = embeddablePlugin + ? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE) + : undefined; + + // Update the layer list with updated geo points upon refresh + useEffect(() => { + async function updateIndexPatternSearchLayer() { + if ( + embeddable && + !isErrorEmbeddable(embeddable) && + Array.isArray(layerList) && + Array.isArray(baseLayers.current) + ) { + embeddable.setLayerList([...baseLayers.current, ...layerList]); + } + } + updateIndexPatternSearchLayer(); + }, [embeddable, layerList]); + + useEffect(() => { + async function setupEmbeddable() { + if (!factory) { + // eslint-disable-next-line no-console + console.error('Map embeddable not found.'); + return; + } + const input: MapEmbeddableInput = { + id: htmlIdGenerator()(), + attributes: { title: '' }, + filters: [], + hidePanelTitles: true, + refreshConfig: { + value: 0, + pause: false, + }, + viewMode: ViewMode.VIEW, + isLayerTOCOpen: false, + hideFilterActions: true, + // Zoom Lat/Lon values are set to make sure map is in center in the panel + // It will also omit Greenland/Antarctica etc. NOTE: Can be removed when initialLocation is set + mapCenter: { + lon: 11, + lat: 20, + zoom: 1, + }, + // can use mapSettings to center map on anomalies + mapSettings: { + disableInteractive: false, + hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, + // Doesn't currently work with GEO_JSON. Will uncomment when https://github.com/elastic/kibana/pull/88294 is in + // initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent + autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query + }, + }; + + const embeddableObject = await factory.create(input); + + if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { + const basemapLayerDescriptor = mapsPlugin + ? await mapsPlugin.createLayerDescriptors.createBasemapLayerDescriptor() + : null; + + if (basemapLayerDescriptor) { + baseLayers.current = [basemapLayerDescriptor]; + await embeddableObject.setLayerList(baseLayers.current); + } + } + + setEmbeddable(embeddableObject); + } + + setupEmbeddable(); + // we want this effect to execute exactly once after the component mounts + }, []); + + useEffect(() => { + if (embeddable && !isErrorEmbeddable(embeddable) && mapEmbeddableInput !== undefined) { + embeddable.updateInput(mapEmbeddableInput); + } + }, [embeddable, mapEmbeddableInput]); + + useEffect(() => { + if (embeddable && !isErrorEmbeddable(embeddable) && renderTooltipContent !== undefined) { + embeddable.setRenderTooltipContent(renderTooltipContent); + } + }, [embeddable, renderTooltipContent]); + + // We can only render after embeddable has already initialized + useEffect(() => { + if (embeddableRoot.current && embeddable) { + embeddable.render(embeddableRoot.current); + } + }, [embeddable, embeddableRoot]); + + if (!embeddablePlugin) { + // eslint-disable-next-line no-console + console.error('Embeddable start plugin not found'); + return null; + } + if (!mapsPlugin) { + // eslint-disable-next-line no-console + console.error('Maps start plugin not found'); + return null; + } + + return ( +
+ ); +} diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts index 4a7550788db56..a97fd513c9c99 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -15,12 +15,16 @@ import { LicenseManagementUIPluginSetup } from '../../../../../license_managemen import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; import { MlServicesContext } from '../../app'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; +import type { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public'; +import { MapsStartApi } from '../../../../../maps/public'; interface StartPlugins { data: DataPublicPluginStart; security?: SecurityPluginSetup; licenseManagement?: LicenseManagementUIPluginSetup; share: SharePluginStart; + embeddable: EmbeddableStart; + maps?: MapsStartApi; } export type StartServices = CoreStart & StartPlugins & { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx index 77f31ae9c2322..a8d8eb18776ec 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { BooleanContent, DateContent, - GeoPointContent, IpContent, KeywordContent, OtherContent, TextContent, NumberContent, } from '../../../stats_table/components/field_data_expanded_row'; +import { GeoPointContent } from './geo_point_content/geo_point_content'; import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts new file mode 100644 index 0000000000000..8a442e345014d --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Feature, Point } from 'geojson'; +import { euiPaletteColorBlind } from '@elastic/eui'; +import { DEFAULT_GEO_REGEX } from './geo_point_content'; +import { SOURCE_TYPES } from '../../../../../../../../maps/common/constants'; + +export const convertWKTGeoToLonLat = ( + value: string | number +): { lat: number; lon: number } | undefined => { + if (typeof value === 'string') { + const trimmedValue = value.trim().replace('POINT (', '').replace(')', ''); + const regExpSerializer = DEFAULT_GEO_REGEX; + const parsed = regExpSerializer.exec(trimmedValue.trim()); + + if (parsed?.groups?.lat != null && parsed?.groups?.lon != null) { + return { + lat: parseFloat(parsed.groups.lat.trim()), + lon: parseFloat(parsed.groups.lon.trim()), + }; + } + } +}; + +export const DEFAULT_POINT_COLOR = euiPaletteColorBlind()[0]; +export const getGeoPointsLayer = ( + features: Array>, + pointColor: string = DEFAULT_POINT_COLOR +) => { + return { + id: 'geo_points', + label: 'Geo points', + sourceDescriptor: { + type: SOURCE_TYPES.GEOJSON_FILE, + __featureCollection: { + features, + type: 'FeatureCollection', + }, + }, + visible: true, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { + color: pointColor, + }, + }, + lineColor: { + type: 'STATIC', + options: { + color: '#fff', + }, + }, + lineWidth: { + type: 'STATIC', + options: { + size: 2, + }, + }, + iconSize: { + type: 'STATIC', + options: { + size: 6, + }, + }, + }, + }, + type: 'VECTOR', + }; +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx new file mode 100644 index 0000000000000..a498b786229bb --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useMemo } from 'react'; + +import { EuiFlexItem } from '@elastic/eui'; +import { Feature, Point } from 'geojson'; +import type { FieldDataRowProps } from '../../../../stats_table/types/field_data_row'; +import { DocumentStatsTable } from '../../../../stats_table/components/field_data_expanded_row/document_stats'; +import { MlEmbeddedMapComponent } from '../../../../../components/ml_embedded_map'; +import { convertWKTGeoToLonLat, getGeoPointsLayer } from './format_utils'; +import { ExpandedRowContent } from '../../../../stats_table/components/field_data_expanded_row/expanded_row_content'; +import { ExamplesList } from '../../../../index_based/components/field_data_row/examples_list'; + +export const DEFAULT_GEO_REGEX = RegExp('(?.+) (?.+)'); + +export const GeoPointContent: FC = ({ config }) => { + const formattedResults = useMemo(() => { + const { stats } = config; + + if (stats === undefined || stats.topValues === undefined) return null; + if (Array.isArray(stats.topValues)) { + const geoPointsFeatures: Array> = []; + + // reformatting the top values from POINT (-2.359207 51.37837) to (-2.359207, 51.37837) + const formattedExamples = []; + + for (let i = 0; i < stats.topValues.length; i++) { + const value = stats.topValues[i]; + const coordinates = convertWKTGeoToLonLat(value.key); + if (coordinates) { + const formattedGeoPoint = `(${coordinates.lat}, ${coordinates.lon})`; + formattedExamples.push(coordinates); + + geoPointsFeatures.push({ + type: 'Feature', + id: `ml-${config.fieldName}-${i}`, + geometry: { + type: 'Point', + coordinates: [coordinates.lat, coordinates.lon], + }, + properties: { + value: formattedGeoPoint, + count: value.doc_count, + }, + }); + } + } + + if (geoPointsFeatures.length > 0) { + return { + examples: formattedExamples, + layerList: [getGeoPointsLayer(geoPointsFeatures)], + }; + } + } + }, [config]); + return ( + + + {formattedResults && Array.isArray(formattedResults.examples) && ( + + + + )} + {formattedResults && Array.isArray(formattedResults.layerList) && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts new file mode 100644 index 0000000000000..d3a50db45ec57 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { GeoPointContent } from './geo_point_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss index 957ca0eec24d3..c1191ad270e4c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss @@ -1 +1 @@ -@import 'file_datavisualizer_view' +@import 'file_datavisualizer_view'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx index 7018f73ff6c32..6d58efee36f36 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx @@ -6,23 +6,31 @@ import React from 'react'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { LoadingIndicator } from '../field_data_row/loading_indicator'; +import { NotInDocsContent } from '../field_data_row/content_types'; import { FieldVisConfig } from '../../../stats_table/types'; import { BooleanContent, DateContent, - GeoPointContent, IpContent, KeywordContent, NumberContent, OtherContent, TextContent, } from '../../../stats_table/components/field_data_expanded_row'; +import { CombinedQuery, GeoPointContent } from './geo_point_content'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import { LoadingIndicator } from '../field_data_row/loading_indicator'; -import { NotInDocsContent } from '../field_data_row/content_types'; - -export const IndexBasedDataVisualizerExpandedRow = ({ item }: { item: FieldVisConfig }) => { +export const IndexBasedDataVisualizerExpandedRow = ({ + item, + indexPattern, + combinedQuery, +}: { + item: FieldVisConfig; + indexPattern: IndexPattern | undefined; + combinedQuery: CombinedQuery; +}) => { const config = item; const { loading, type, existsInDocs, fieldName } = config; @@ -42,7 +50,13 @@ export const IndexBasedDataVisualizerExpandedRow = ({ item }: { item: FieldVisCo return ; case ML_JOB_FIELD_TYPES.GEO_POINT: - return ; + return ( + + ); case ML_JOB_FIELD_TYPES.IP: return ; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx new file mode 100644 index 0000000000000..94db5d1ba686c --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useEffect, useState } from 'react'; + +import { EuiFlexItem } from '@elastic/eui'; +import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; +import { FieldVisConfig } from '../../../stats_table/types'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { MlEmbeddedMapComponent } from '../../../../components/ml_embedded_map'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { ES_GEO_FIELD_TYPE } from '../../../../../../../maps/common/constants'; +import { LayerDescriptor } from '../../../../../../../maps/common/descriptor_types'; +import { useMlKibana } from '../../../../contexts/kibana'; +import { DocumentStatsTable } from '../../../stats_table/components/field_data_expanded_row/document_stats'; +import { ExpandedRowContent } from '../../../stats_table/components/field_data_expanded_row/expanded_row_content'; + +export interface CombinedQuery { + searchString: string | { [key: string]: any }; + searchQueryLanguage: string; +} +export const GeoPointContent: FC<{ + config: FieldVisConfig; + indexPattern: IndexPattern | undefined; + combinedQuery: CombinedQuery; +}> = ({ config, indexPattern, combinedQuery }) => { + const { stats } = config; + const [layerList, setLayerList] = useState([]); + const { + services: { maps: mapsPlugin }, + } = useMlKibana(); + + // Update the layer list with updated geo points upon refresh + useEffect(() => { + async function updateIndexPatternSearchLayer() { + if ( + indexPattern?.id !== undefined && + config !== undefined && + config.fieldName !== undefined && + config.type === ML_JOB_FIELD_TYPES.GEO_POINT + ) { + const params = { + indexPatternId: indexPattern.id, + geoFieldName: config.fieldName, + geoFieldType: config.type as ES_GEO_FIELD_TYPE.GEO_POINT, + query: { + query: combinedQuery.searchString, + language: combinedQuery.searchQueryLanguage, + }, + }; + const searchLayerDescriptor = mapsPlugin + ? await mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor(params) + : null; + if (searchLayerDescriptor) { + setLayerList([...layerList, searchLayerDescriptor]); + } + } + } + updateIndexPatternSearchLayer(); + }, [indexPattern, config.fieldName, combinedQuery]); + + if (stats?.examples === undefined) return null; + return ( + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx index 1e8f7586258d5..a59c6e0fe4183 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx @@ -15,25 +15,29 @@ interface Props { } export const ExamplesList: FC = ({ examples }) => { - if ( - examples === undefined || - examples === null || - !Array.isArray(examples) || - examples.length === 0 - ) { + if (examples === undefined || examples === null || !Array.isArray(examples)) { return null; } - - const examplesContent = examples.map((example, i) => { - return ( - ); - }); + } else { + examplesContent = examples.map((example, i) => { + return ( + + ); + }); + } return (
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx index bd83aaa1f6149..8ad48a3e60d3a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx @@ -19,9 +19,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import classNames from 'classnames'; import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format'; import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place'; +import { ExpandedRowFieldHeader } from '../../../../stats_table/components/expanded_row_field_header'; +import { FieldVisStats } from '../../../../stats_table/types'; interface Props { - stats: any; + stats: FieldVisStats | undefined; fieldFormat?: any; barColor?: 'primary' | 'secondary' | 'danger' | 'subdued' | 'accent'; compressed?: boolean; @@ -37,6 +39,7 @@ function getPercentLabel(docCount: number, topValuesSampleSize: number): string } export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed }) => { + if (stats === undefined) return null; const { topValues, topValuesSampleSize, @@ -46,51 +49,64 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed } = stats; const progressBarMax = isTopValuesSampled === true ? topValuesSampleSize : count; return ( -
- {Array.isArray(topValues) && - topValues.map((value: any) => ( - - + + + + +
+ {Array.isArray(topValues) && + topValues.map((value) => ( + + + + + {kibanaFieldFormat(value.key, fieldFormat)} + + + + + + + {progressBarMax !== undefined && ( + + + {getPercentLabel(value.doc_count, progressBarMax)} + + )} - > - - - {kibanaFieldFormat(value.key, fieldFormat)} - - - - - - - - - {getPercentLabel(value.doc_count, progressBarMax)} - - - - ))} - {isTopValuesSampled === true && ( - - - - - - - )} -
+
+ ))} + {isTopValuesSampled === true && ( + + + + + + + )} +
+ ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index e5b243d524034..8cf5e28ad3b5b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useEffect, useMemo, useState } from 'react'; +import React, { FC, Fragment, useEffect, useMemo, useState, useCallback } from 'react'; import { merge } from 'rxjs'; import { EuiFlexGroup, @@ -112,19 +112,6 @@ export const getDefaultDataVisualizerListState = (): Required { - const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); - if (item !== undefined) { - m[fieldName] = ; - } - return m; - }, {} as ItemIdToExpandedRowMap); -} - export const Page: FC = () => { const mlContext = useMlContext(); const restorableDefaults = getDefaultDataVisualizerListState(); @@ -678,6 +665,26 @@ export const Page: FC = () => { } return { visibleFieldsCount: _visibleFieldsCount, totalFieldsCount: _totalFieldsCount }; }, [overallStats, showEmptyFields]); + + const getItemIdToExpandedRowMap = useCallback( + function (itemIds: string[], items: FieldVisConfig[]): ItemIdToExpandedRowMap { + return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { + const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); + if (item !== undefined) { + m[fieldName] = ( + + ); + } + return m; + }, {} as ItemIdToExpandedRowMap); + }, + [currentIndexPattern, searchQuery] + ); + const { services: { docLinks }, } = useMlKibana(); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss index 2ab26f1564a1f..1832b0f78b895 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss @@ -60,6 +60,21 @@ @include euiCodeFont; } + .mlFieldDataCard__geoContent { + z-index: auto; + flex: 1; + display: flex; + flex-direction: column; + height: 100%; + position: relative; + .embPanel__content { + display: flex; + flex: 1 1 100%; + z-index: 1; + min-height: 0; // Absolute must for Firefox to scroll contents + } + } + .mlFieldDataCard__stats { padding: $euiSizeS $euiSizeS 0 $euiSizeS; text-align: center; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss index 6e7e66db9e03a..9e838c180713f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss @@ -1,5 +1,6 @@ -@import 'components/field_data_expanded_row/number_content'; +@import 'components/field_data_expanded_row/index'; @import 'components/field_count_stats/index'; +@import 'components/field_data_row/index'; .mlDataVisualizerFieldExpandedRow { padding-left: $euiSize * 4; @@ -37,6 +38,7 @@ } .mlDataVisualizerSummaryTable { max-width: 350px; + min-width: 250px; .euiTableRow > .euiTableRowCell { border-bottom: 0; } @@ -45,6 +47,10 @@ } } .mlDataVisualizerSummaryTableWrapper { - max-width: 350px; + max-width: 300px; + } + .mlDataVisualizerMapWrapper { + min-height: 300px; + min-width: 600px; } } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss index fdc591a140fea..799beec093cca 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss @@ -1 +1,7 @@ @import 'number_content'; + +.mlDataVisualizerExpandedRow { + @include euiBreakpoint('xs', 's', 'm') { + flex-direction: column; + } +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx index a75920dd09b34..70e98153fd55c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx @@ -5,7 +5,7 @@ */ import React, { FC, ReactNode, useMemo } from 'react'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { Axis, BarSeries, Chart, Settings } from '@elastic/charts'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -16,6 +16,7 @@ import { getTFPercentage } from '../../utils'; import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; import { useDataVizChartTheme } from '../../hooks'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; function getPercentLabel(value: number): string { if (value === 0) { @@ -85,7 +86,7 @@ export const BooleanContent: FC = ({ config }) => { ); return ( - + @@ -138,6 +139,6 @@ export const BooleanContent: FC = ({ config }) => { /> - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx index 8d122df628381..a6e7901df4e8e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx @@ -5,7 +5,7 @@ */ import React, { FC, ReactNode } from 'react'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; const TIME_FORMAT = 'MMM D YYYY, HH:mm:ss.SSS'; interface SummaryTableItem { function: string; @@ -66,7 +67,7 @@ export const DateContent: FC = ({ config }) => { ]; return ( - + {summaryTableTitle} @@ -80,6 +81,6 @@ export const DateContent: FC = ({ config }) => { tableLayout="auto" /> - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx index 177ac722166f7..fd23e9d51bf4e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { FieldDataRowProps } from '../../types'; +import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; const metaTableColumns = [ { @@ -59,7 +60,7 @@ export const DocumentStatsTable: FC = ({ config }) => { defaultMessage="percentage" /> ), - value: `${(count / sampleCount) * 100}%`, + value: `${roundToDecimalPlace((count / sampleCount) * 100)}%`, }, { function: 'distinctValues', diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx new file mode 100644 index 0000000000000..6c14bd7a64a94 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, ReactNode } from 'react'; +import { EuiFlexGroup } from '@elastic/eui'; + +interface Props { + children: ReactNode; + dataTestSubj: string; +} +export const ExpandedRowContent: FC = ({ children, dataTestSubj }) => { + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx deleted file mode 100644 index 993c7a94f5e06..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx +++ /dev/null @@ -1,35 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { FieldDataRowProps } from '../../types/field_data_row'; -import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; -import { DocumentStatsTable } from './document_stats'; -import { TopValues } from '../../../index_based/components/field_data_row/top_values'; - -export const GeoPointContent: FC = ({ config }) => { - const { stats } = config; - if (stats === undefined || (stats?.examples === undefined && stats?.topValues === undefined)) - return null; - - return ( - - - {Array.isArray(stats.examples) && ( - - - - )} - {Array.isArray(stats.topValues) && ( - - - - )} - - ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts index c6cd50f6bc2e9..b9b1e181343b7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts @@ -6,7 +6,7 @@ export { BooleanContent } from './boolean_content'; export { DateContent } from './date_content'; -export { GeoPointContent } from './geo_point_content'; +export { GeoPointContent } from '../../../file_based/components/expanded_row/geo_point_content/geo_point_content'; export { KeywordContent } from './keyword_content'; export { IpContent } from './ip_content'; export { NumberContent } from './number_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx index 79492bb44a2dc..183268d6a7ef8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx @@ -5,14 +5,10 @@ */ import React, { FC } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - import type { FieldDataRowProps } from '../../types/field_data_row'; import { TopValues } from '../../../index_based/components/field_data_row/top_values'; -import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; export const IpContent: FC = ({ config }) => { const { stats } = config; @@ -22,17 +18,9 @@ export const IpContent: FC = ({ config }) => { const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; return ( - + - - - - - - - + + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx index 634f5b55513a3..d11ecc7d8804b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx @@ -5,31 +5,20 @@ */ import React, { FC } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { TopValues } from '../../../index_based/components/field_data_row/top_values'; -import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; export const KeywordContent: FC = ({ config }) => { const { stats } = config; const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; return ( - + - - - - - - - - + + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx index d05de26d3c5d4..56811e61ad891 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx @@ -5,7 +5,7 @@ */ import React, { FC, ReactNode, useEffect, useState } from 'react'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexItem, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -20,6 +20,7 @@ import { import { TopValues } from '../../../index_based/components/field_data_row/top_values'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; const METRIC_DISTRIBUTION_CHART_WIDTH = 325; const METRIC_DISTRIBUTION_CHART_HEIGHT = 200; @@ -97,7 +98,7 @@ export const NumberContent: FC = ({ config }) => { } ); return ( - + {summaryTableTitle} @@ -112,24 +113,7 @@ export const NumberContent: FC = ({ config }) => { {stats && ( - - - - - - - - - - + )} {distribution && ( @@ -164,6 +148,6 @@ export const NumberContent: FC = ({ config }) => { )} - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx index a6d7398990cd3..08884619db2d6 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx @@ -5,18 +5,18 @@ */ import React, { FC } from 'react'; -import { EuiFlexGroup } from '@elastic/eui'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; export const OtherContent: FC = ({ config }) => { const { stats } = config; if (stats === undefined) return null; return ( - + {Array.isArray(stats.examples) && } - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx index 55639ecc5761f..c0a5ca18d03b5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx @@ -5,13 +5,14 @@ */ import React, { FC, Fragment } from 'react'; -import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; +import { ExpandedRowContent } from './expanded_row_content'; export const TextContent: FC = ({ config }) => { const { stats } = config; @@ -23,7 +24,7 @@ export const TextContent: FC = ({ config }) => { const numExamples = examples.length; return ( - + {numExamples > 0 && } {numExamples === 0 && ( @@ -59,6 +60,6 @@ export const TextContent: FC = ({ config }) => { )} - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss new file mode 100644 index 0000000000000..27483feb573b8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss @@ -0,0 +1,3 @@ +.mlDataVisualizerColumnHeaderIcon { + max-width: $euiSizeM; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx index 819d5278c0d78..44c028d1ba8b5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx @@ -12,7 +12,7 @@ export const DistinctValues = ({ cardinality }: { cardinality?: number }) => { if (cardinality === undefined) return null; return ( - + diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx index 9c89d74fa751b..b223fd5d98c1a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx @@ -21,7 +21,7 @@ export const DocumentStat = ({ config }: FieldDataRowProps) => { return ( - + diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx index 3a84ae644cb4e..996fffd225f96 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx @@ -14,6 +14,7 @@ import { } from '../metric_distribution_chart'; import { formatSingleValue } from '../../../../formatters/format_value'; import { FieldVisConfig } from '../../types'; +import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format'; const METRIC_DISTRIBUTION_CHART_WIDTH = 150; const METRIC_DISTRIBUTION_CHART_HEIGHT = 80; @@ -59,14 +60,16 @@ export const IndexBasedNumberContentPreview: FC = ({ <> - {legendText.min} + + {kibanaFieldFormat(legendText.min, fieldFormat)} + - {legendText.max} + {kibanaFieldFormat(legendText.max, fieldFormat)} diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index d7ca0203ab69e..9aa16f521554c 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -21,6 +21,7 @@ import type { import type { IndexPatternsContract, DataPublicPluginStart } from 'src/plugins/data/public'; import type { SharePluginStart } from 'src/plugins/share/public'; import type { SecurityPluginSetup } from '../../../../security/public'; +import type { MapsStartApi } from '../../../../maps/public'; export interface DependencyCache { timefilter: DataPublicPluginSetup['query']['timefilter'] | null; @@ -40,6 +41,7 @@ export interface DependencyCache { security: SecurityPluginSetup | undefined | null; i18n: I18nStart | null; urlGenerators: SharePluginStart['urlGenerators'] | null; + maps: MapsStartApi | null; } const cache: DependencyCache = { @@ -60,6 +62,7 @@ const cache: DependencyCache = { security: null, i18n: null, urlGenerators: null, + maps: null, }; export function setDependencyCache(deps: Partial) { diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 7c32671be93c4..3ba79e0eb9187 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -25,7 +25,7 @@ import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { HomePublicPluginSetup } from 'src/plugins/home/public'; import type { IndexPatternManagementSetup } from 'src/plugins/index_pattern_management/public'; -import type { EmbeddableSetup } from 'src/plugins/embeddable/public'; +import type { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import type { SpacesPluginStart } from '../../spaces/public'; import { AppStatus, AppUpdater, DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; @@ -45,6 +45,7 @@ import { setDependencyCache } from './application/util/dependency_cache'; import { registerFeature } from './register_feature'; // Not importing from `ml_url_generator/index` here to avoid importing unnecessary code import { registerUrlGenerator } from './ml_url_generator/ml_url_generator'; +import type { MapsStartApi } from '../../maps/public'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -52,6 +53,8 @@ export interface MlStartDependencies { kibanaLegacy: KibanaLegacyStart; uiActions: UiActionsStart; spaces?: SpacesPluginStart; + embeddable: EmbeddableStart; + maps?: MapsStartApi; } export interface MlSetupDependencies { security?: SecurityPluginSetup; @@ -102,7 +105,8 @@ export class MlPlugin implements Plugin { usageCollection: pluginsSetup.usageCollection, licenseManagement: pluginsSetup.licenseManagement, home: pluginsSetup.home, - embeddable: pluginsSetup.embeddable, + embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, + maps: pluginsStart.maps, uiActions: pluginsStart.uiActions, kibanaVersion, }, diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 7b3f97b684edc..22e75ec694733 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -1189,7 +1189,8 @@ export class DataVisualizer { }); const searchBody = { - _source: field, + fields: [field], + _source: false, query: { bool: { filter: filterCriteria, @@ -1209,16 +1210,16 @@ export class DataVisualizer { if (body.hits.total.value > 0) { const hits = body.hits.hits; for (let i = 0; i < hits.length; i++) { - // Look in the _source for the field value. - // If the field is not in the _source (as will happen if the - // field is populated using copy_to in the index mapping), - // there will be no example to add. // Use lodash get() to support field names containing dots. - const example: any = get(hits[i]._source, field); - if (example !== undefined && stats.examples.indexOf(example) === -1) { - stats.examples.push(example); - if (stats.examples.length === maxExamples) { - break; + const doc: object[] | undefined = get(hits[i].fields, field); + // the results from fields query is always an array + if (Array.isArray(doc) && doc.length > 0) { + const example = doc[0]; + if (example !== undefined && stats.examples.indexOf(example) === -1) { + stats.examples.push(example); + if (stats.examples.length === maxExamples) { + break; + } } } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 777279b4646b5..4e42a183fad6b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13117,8 +13117,6 @@ "xpack.ml.featureRegistry.mlFeatureName": "機械学習", "xpack.ml.fieldDataCard.cardBoolean.valuesLabel": "値", "xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "まとめ", - "xpack.ml.fieldDataCard.cardIp.topValuesLabel": "トップの値", - "xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "トップの値", "xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "たとえば、ドキュメントマッピングで {copyToParam} パラメーターを使ったり、{includesParam} と {excludesParam} パラメーターを使用してインデックスした後に {sourceParam} フィールドから切り取ったりして入力される場合があります。", "xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "このフィールドはクエリが実行されたドキュメントの {sourceParam} フィールドにありませんでした。", "xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "このフィールドの例が取得されませんでした", @@ -13136,7 +13134,6 @@ "xpack.ml.fieldDataCardExpandedRow.numberContent.medianLabel": "中間", "xpack.ml.fieldDataCardExpandedRow.numberContent.minLabel": "分", "xpack.ml.fieldDataCardExpandedRow.numberContent.summaryTableTitle": "まとめ", - "xpack.ml.fieldDataCardExpandedRow.numberContent.topValuesTitle": "トップの値", "xpack.ml.fieldTitleBar.documentCountLabel": "ドキュメントカウント", "xpack.ml.fieldTypeIcon.booleanTypeAriaLabel": "ブールタイプ", "xpack.ml.fieldTypeIcon.dateTypeAriaLabel": "日付タイプ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 92f0db5c372f1..d12ed1be87972 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13148,8 +13148,6 @@ "xpack.ml.featureRegistry.mlFeatureName": "Machine Learning", "xpack.ml.fieldDataCard.cardBoolean.valuesLabel": "值", "xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "摘要", - "xpack.ml.fieldDataCard.cardIp.topValuesLabel": "排名最前值", - "xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "排名最前值", "xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "例如,可以使用文档映射中的 {copyToParam} 参数进行填充,也可以在索引后通过使用 {includesParam} 和 {excludesParam} 参数从 {sourceParam} 字段中修剪。", "xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "查询的文档的 {sourceParam} 字段中不存在此字段。", "xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "没有获取此字段的示例", @@ -13167,7 +13165,6 @@ "xpack.ml.fieldDataCardExpandedRow.numberContent.medianLabel": "中值", "xpack.ml.fieldDataCardExpandedRow.numberContent.minLabel": "最小值", "xpack.ml.fieldDataCardExpandedRow.numberContent.summaryTableTitle": "摘要", - "xpack.ml.fieldDataCardExpandedRow.numberContent.topValuesTitle": "排名最前值", "xpack.ml.fieldTitleBar.documentCountLabel": "文档计数", "xpack.ml.fieldTypeIcon.booleanTypeAriaLabel": "布尔类型", "xpack.ml.fieldTypeIcon.dateTypeAriaLabel": "日期类型", diff --git a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts index 531eba54f931d..10926a831d36b 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts @@ -112,6 +112,47 @@ export default function ({ getService }: FtrProviderContext) { fieldNameFiltersResultCount: 1, }, }, + { + suiteSuffix: 'with a file containing geo field', + filePath: path.join(__dirname, 'files_to_import', 'geo_file.csv'), + indexName: 'user-import_2', + createIndexPattern: false, + fieldTypeFilters: [ML_JOB_FIELD_TYPES.GEO_POINT], + fieldNameFilters: ['Coordinates'], + expected: { + results: { + title: 'geo_file.csv', + numberOfFields: 3, + }, + metricFields: [], + nonMetricFields: [ + { + fieldName: 'Context', + type: ML_JOB_FIELD_TYPES.UNKNOWN, + docCountFormatted: '0 (0%)', + exampleCount: 0, + }, + { + fieldName: 'Coordinates', + type: ML_JOB_FIELD_TYPES.GEO_POINT, + docCountFormatted: '13 (100%)', + exampleCount: 7, + }, + { + fieldName: 'Location', + type: ML_JOB_FIELD_TYPES.KEYWORD, + docCountFormatted: '13 (100%)', + exampleCount: 7, + }, + ], + visibleMetricFieldsCount: 0, + totalMetricFieldsCount: 0, + populatedFieldsCount: 3, + totalFieldsCount: 3, + fieldTypeFiltersResultCount: 1, + fieldNameFiltersResultCount: 1, + }, + }, ]; const testDataListNegative = [ diff --git a/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv b/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv new file mode 100644 index 0000000000000..df7417f474d83 --- /dev/null +++ b/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv @@ -0,0 +1,14 @@ +Coordinates,Location,Context +POINT (-2.516919 51.423683),On or near A4175, +POINT (-2.515072 51.419357),On or near Stockwood Hill, +POINT (-2.509126 51.416137),On or near St Francis Road, +POINT (-2.509384 51.40959),On or near Barnard Walk, +POINT (-2.509126 51.416137),On or near St Francis Road, +POINT (-2.516919 51.423683),On or near A4175, +POINT (-2.511571 51.414895),On or near Orchard Close, +POINT (-2.534338 51.417697),On or near Scotland Lane, +POINT (-2.509384 51.40959),On or near Barnard Walk, +POINT (-2.495055 51.422132),On or near Cross Street, +POINT (-2.509384 51.40959),On or near Barnard Walk, +POINT (-2.495055 51.422132),On or near Cross Street, +POINT (-2.509126 51.416137),On or near St Francis Road, \ No newline at end of file diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 0833f84960ea6..01d7ca6af4cc3 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -24,6 +24,11 @@ interface TestData { sourceIndexOrSavedSearch: string; fieldNameFilters: string[]; fieldTypeFilters: string[]; + rowsPerPage?: 10 | 25 | 50; + sampleSizeValidations: Array<{ + size: number; + expected: { field: string; docCountFormatted: string }; + }>; expected: { totalDocCountFormatted: string; metricFields?: MetricFieldVisConfig[]; @@ -47,6 +52,10 @@ export default function ({ getService }: FtrProviderContext) { sourceIndexOrSavedSearch: 'ft_farequote', fieldNameFilters: ['airline', '@timestamp'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.KEYWORD], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + ], expected: { totalDocCountFormatted: '86,274', metricFields: [ @@ -132,6 +141,10 @@ export default function ({ getService }: FtrProviderContext) { sourceIndexOrSavedSearch: 'ft_farequote_kuery', fieldNameFilters: ['@version'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.DATE, ML_JOB_FIELD_TYPES.TEXT], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + ], expected: { totalDocCountFormatted: '34,415', metricFields: [ @@ -217,6 +230,10 @@ export default function ({ getService }: FtrProviderContext) { sourceIndexOrSavedSearch: 'ft_farequote_lucene', fieldNameFilters: ['@version.keyword', 'type'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + ], expected: { totalDocCountFormatted: '34,416', metricFields: [ @@ -297,6 +314,41 @@ export default function ({ getService }: FtrProviderContext) { }, }; + const sampleLogTestData: TestData = { + suiteTitle: 'geo point field', + sourceIndexOrSavedSearch: 'ft_module_sample_logs', + fieldNameFilters: ['geo.coordinates'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.GEO_POINT], + rowsPerPage: 50, + expected: { + totalDocCountFormatted: '408', + metricFields: [], + // only testing the geo_point fields + nonMetricFields: [ + { + fieldName: 'geo.coordinates', + type: ML_JOB_FIELD_TYPES.GEO_POINT, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '408 (100%)', + exampleCount: 10, + }, + ], + emptyFields: [], + visibleMetricFieldsCount: 4, + totalMetricFieldsCount: 5, + populatedFieldsCount: 35, + totalFieldsCount: 36, + fieldNameFiltersResultCount: 1, + fieldTypeFiltersResultCount: 1, + }, + sampleSizeValidations: [ + { size: 1000, expected: { field: 'geo.coordinates', docCountFormatted: '408 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '408 (100%)' } }, + ], + }; + function runTests(testData: TestData) { it(`${testData.suiteTitle} loads the source data in the data visualizer`, async () => { await ml.testExecution.logTestStep( @@ -332,6 +384,10 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); + if (testData.rowsPerPage) { + await ml.dataVisualizerTable.ensureNumRowsPerPage(testData.rowsPerPage); + } + await ml.dataVisualizerTable.assertSearchPanelExist(); await ml.dataVisualizerTable.assertSampleSizeInputExists(); await ml.dataVisualizerTable.assertFieldTypeInputExists(); @@ -376,8 +432,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep( `${testData.suiteTitle} sample size control changes non-metric fields` ); - await ml.dataVisualizerTable.setSampleSizeInputValue(1000, 'airline', '1000 (100%)'); - await ml.dataVisualizerTable.setSampleSizeInputValue(5000, '@timestamp', '5000 (100%)'); + for (const sampleSizeCase of testData.sampleSizeValidations) { + const { size, expected } = sampleSizeCase; + await ml.dataVisualizerTable.setSampleSizeInputValue( + size, + expected.field, + expected.docCountFormatted + ); + } await ml.testExecution.logTestStep('sets and resets field type filter correctly'); await ml.dataVisualizerTable.setFieldTypeFilter( @@ -411,7 +473,10 @@ export default function ({ getService }: FtrProviderContext) { this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); + await esArchiver.loadIfNeeded('ml/module_sample_logs'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.createIndexPatternIfNeeded('ft_module_sample_logs', '@timestamp'); await ml.testResources.createSavedSearchFarequoteLuceneIfNeeded(); await ml.testResources.createSavedSearchFarequoteKueryIfNeeded(); await ml.testResources.setKibanaTimeZoneToUTC(); @@ -447,5 +512,15 @@ export default function ({ getService }: FtrProviderContext) { runTests(farequoteLuceneSearchTestData); }); + + describe('with module_sample_logs ', function () { + // Run tests on full farequote index. + it(`${sampleLogTestData.suiteTitle} loads the data visualizer selector page`, async () => { + // Start navigation from the base of the ML app. + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataVisualizer(); + }); + runTests(sampleLogTestData); + }); }); } diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index ad4625ed4dcb4..4772b3c894471 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -288,6 +288,16 @@ export function MachineLearningDataVisualizerTableProvider( await this.ensureDetailsClosed(fieldName); } + public async assertExamplesList(fieldName: string, expectedExamplesCount: number) { + const examplesList = await testSubjects.find( + this.detailsSelector(fieldName, 'mlFieldDataExamplesList') + ); + const examplesListItems = await examplesList.findAllByTagName('li'); + expect(examplesListItems).to.have.length( + expectedExamplesCount, + `Expected example list item count for field '${fieldName}' to be '${expectedExamplesCount}' (got '${examplesListItems.length}')` + ); + } public async assertTextFieldContents( fieldName: string, docCountFormatted: string, @@ -297,14 +307,33 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertFieldDocCount(fieldName, docCountFormatted); await this.ensureDetailsOpen(fieldName); - const examplesList = await testSubjects.find( - this.detailsSelector(fieldName, 'mlFieldDataExamplesList') - ); - const examplesListItems = await examplesList.findAllByTagName('li'); - expect(examplesListItems).to.have.length( - expectedExamplesCount, - `Expected example list item count for field '${fieldName}' to be '${expectedExamplesCount}' (got '${examplesListItems.length}')` - ); + await this.assertExamplesList(fieldName, expectedExamplesCount); + await this.ensureDetailsClosed(fieldName); + } + + public async assertGeoPointFieldContents( + fieldName: string, + docCountFormatted: string, + expectedExamplesCount: number + ) { + await this.assertRowExists(fieldName); + await this.assertFieldDocCount(fieldName, docCountFormatted); + await this.ensureDetailsOpen(fieldName); + + await this.assertExamplesList(fieldName, expectedExamplesCount); + + await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mlEmbeddedMapContent')); + + await this.ensureDetailsClosed(fieldName); + } + + public async assertUnknownFieldContents(fieldName: string, docCountFormatted: string) { + await this.assertRowExists(fieldName); + await this.assertFieldDocCount(fieldName, docCountFormatted); + await this.ensureDetailsOpen(fieldName); + + await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mlDVDocumentStatsContent')); + await this.ensureDetailsClosed(fieldName); } @@ -321,10 +350,14 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertKeywordFieldContents(fieldName, docCountFormatted, exampleCount); } else if (fieldType === ML_JOB_FIELD_TYPES.TEXT) { await this.assertTextFieldContents(fieldName, docCountFormatted, exampleCount); + } else if (fieldType === ML_JOB_FIELD_TYPES.GEO_POINT) { + await this.assertGeoPointFieldContents(fieldName, docCountFormatted, exampleCount); + } else if (fieldType === ML_JOB_FIELD_TYPES.UNKNOWN) { + await this.assertUnknownFieldContents(fieldName, docCountFormatted); } } - public async ensureNumRowsPerPage(n: 10 | 25 | 100) { + public async ensureNumRowsPerPage(n: 10 | 25 | 50) { const paginationButton = 'mlDataVisualizerTable > tablePaginationPopoverButton'; await retry.tryForTime(10000, async () => { await testSubjects.existOrFail(paginationButton); From eb0c72dde27117de0e326ec11901574b2f20e0ea Mon Sep 17 00:00:00 2001 From: Kent Marten <65553677+kmartastic@users.noreply.github.com> Date: Wed, 27 Jan 2021 14:35:43 -0800 Subject: [PATCH 3/4] [maps] Update point to point docs with use cases (#89246) (#89495) Co-authored-by: Kent Marten Co-authored-by: Kent Marten --- docs/maps/maps-aggregations.asciidoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index f09d99250c22d..3c66e187bf59c 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -98,6 +98,13 @@ Point to point uses an {es} {ref}/search-aggregations-bucket-terms-aggregation.h Then, a nested {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] groups sources for each destination into grids. A line connects each source grid centroid to each destination. +Point-to-point layers are used in several common use cases: + +* Source-destination maps for network traffic +* Origin-destination maps for flight data +* Origin-destination flows for import/export/migration +* Origin-destination for pick-up/drop-off data + image::maps/images/point_to_point.png[] [role="xpack"] From cce690181d44b0eac3056175df6a9b12bad9b900 Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Wed, 27 Jan 2021 16:41:17 -0600 Subject: [PATCH 4/4] Address intermittent test failure (#89367) (#89470) After observing the conditions when this test fails, it appears that some (but not all) signals are available. As these signals are generated by a rule via a bulk create, the odds of us retrieving signals in the middle of that bulk creation is very slim (but not impossible). The crux of the error here was: we wait for signals to be generated, but not the ones that we need. Specifically, we are waiting for a single signal to be available, but since we are asserting on sequences of signals, we need several to be available to us. While not perfect (because the signals we receive are not technically guaranteed to be sequence signals), increasing the number of signals that we wait for before proceeding should be sufficient to prevent this failure state. In debugging, it was observed that every test returning 9-10 signals succeeded, while it was possible for the test to return only one signal and fail. --- .../security_and_spaces/tests/generating_signals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index c3c7ecd0aba81..00a20abe367ae 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -265,7 +265,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); + await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByRuleIds(supertest, ['eql-rule']); const sequenceSignal = signalsOpen.hits.hits.find( (signal) => signal._source.signal.depth === 2