From 15abdec708f1176ad17adc47f7b4f23a220aa1ad Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Fri, 4 Jun 2021 14:22:05 -0500 Subject: [PATCH 01/11] [ML] Add index pattern editor flyout --- .../date_picker_wrapper.tsx | 4 +- .../field_data_row/action_menu/actions.ts | 59 ++++++-- .../index_data_visualizer_view.tsx | 67 ++++++--- .../index_pattern_management/index.ts | 8 ++ .../index_pattern_management.tsx | 127 ++++++++++++++++++ .../services/timefilter_refresh_service.ts | 3 +- .../public/application/kibana_context.ts | 3 +- 7 files changed, 242 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index.ts create mode 100644 x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx index f6f53f40d6b9eb..52ae5e685316dc 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx @@ -18,7 +18,7 @@ import { import { useUrlState } from '../../util/url_state'; import { useDataVisualizerKibana } from '../../../kibana_context'; -import { dataVisualizerTimefilterRefresh$ } from '../../../index_data_visualizer/services/timefilter_refresh_service'; +import { dataVisualizerRefresh$ } from '../../../index_data_visualizer/services/timefilter_refresh_service'; interface TimePickerQuickRange { from: string; @@ -50,7 +50,7 @@ function getRecentlyUsedRangesFactory(timeHistory: TimeHistoryContract) { } function updateLastRefresh(timeRange: OnRefreshProps) { - dataVisualizerTimefilterRefresh$.next({ lastRefresh: Date.now(), timeRange }); + dataVisualizerRefresh$.next({ lastRefresh: Date.now(), timeRange }); } export const DatePickerWrapper: FC = () => { diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 414c72c33f057d..187da24f54cbdf 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -7,19 +7,31 @@ import { i18n } from '@kbn/i18n'; import { Action } from '@elastic/eui/src/components/basic_table/action_types'; +import { MutableRefObject } from 'react'; import { getCompatibleLensDataType, getLensAttributes } from './lens_utils'; import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; import { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; import { FieldVisConfig } from '../../stats_table/types'; -import { LensPublicStart } from '../../../../../../../lens/public'; +import { DataVisualizerKibanaReactContextValue } from '../../../../kibana_context'; +import { + dataVisualizerRefresh$, + Refresh, +} from '../../../../index_data_visualizer/services/timefilter_refresh_service'; + export function getActions( indexPattern: IndexPattern, - lensPlugin: LensPublicStart, - combinedQuery: CombinedQuery + services: DataVisualizerKibanaReactContextValue['services'], + combinedQuery: CombinedQuery, + actionFlyoutRef: MutableRefObject<(() => void | undefined) | undefined> ): Array> { - const canUseLensEditor = lensPlugin.canUseEditor(); - return [ - { + const { lens: lensPlugin, indexPatternFieldEditor } = services; + + const actions: Array> = []; + + // Navigate to Lens with prefilled chart for data field + if (lensPlugin !== undefined) { + const canUseLensEditor = lensPlugin?.canUseEditor(); + actions.push({ name: i18n.translate('xpack.dataVisualizer.index.dataGrid.exploreInLensTitle', { defaultMessage: 'Explore in Lens', }), @@ -40,6 +52,37 @@ export function getActions( } }, 'data-test-subj': 'dataVisualizerActionViewInLensButton', - }, - ]; + }); + } + + // Allow to edit index pattern field + if (indexPatternFieldEditor?.userPermissions.editIndexPattern()) { + actions.push({ + name: i18n.translate('xpack.dataVisualizer.index.dataGrid.editIndexPatternFieldTitle', { + defaultMessage: 'Edit index pattern field', + }), + description: i18n.translate( + 'xpack.dataVisualizer.index.dataGrid.editIndexPatternFieldDescription', + { + defaultMessage: 'Edit index pattern field', + } + ), + type: 'icon', + icon: 'indexEdit', + onClick: (item: FieldVisConfig) => { + actionFlyoutRef.current = indexPatternFieldEditor?.openEditor({ + ctx: { indexPattern }, + fieldName: item.fieldName, + onSave: () => { + const refresh: Refresh = { + lastRefresh: Date.now(), + }; + dataVisualizerRefresh$.next(refresh); + }, + }); + }, + 'data-test-subj': 'dataVisualizerActionEditIndexPatternFieldButton', + }); + } + return actions; } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index 12441bcfbbb235..831f7044af51fc 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC, Fragment, useEffect, useMemo, useState, useCallback } from 'react'; +import React, { FC, Fragment, useEffect, useMemo, useState, useCallback, useRef } from 'react'; import { merge } from 'rxjs'; import { EuiFlexGroup, @@ -62,10 +62,11 @@ import { kbnTypeToJobType } from '../../../common/util/field_types_utils'; import { SearchPanel } from '../search_panel'; import { ActionsPanel } from '../actions_panel'; import { DatePickerWrapper } from '../../../common/components/date_picker_wrapper'; -import { dataVisualizerTimefilterRefresh$ } from '../../services/timefilter_refresh_service'; +import { dataVisualizerRefresh$ } from '../../services/timefilter_refresh_service'; import { HelpMenu } from '../../../common/components/help_menu'; import { TimeBuckets } from '../../services/time_buckets'; import { extractSearchData } from '../../utils/saved_search_utils'; +import { DataVisualizerIndexPatternManagement } from '../index_pattern_management'; interface DataVisualizerPageState { overallStats: OverallStats; @@ -123,9 +124,8 @@ export interface IndexDataVisualizerViewProps { const restorableDefaults = getDefaultDataVisualizerListState(); export const IndexDataVisualizerView: FC = (dataVisualizerProps) => { - const { - services: { lens: lensPlugin, docLinks, notifications, uiSettings }, - } = useDataVisualizerKibana(); + const { services } = useDataVisualizerKibana(); + const { docLinks, notifications, uiSettings } = services; const { toasts } = notifications; const [dataVisualizerListState, setDataVisualizerListState] = usePageUrlState( @@ -299,7 +299,7 @@ export const IndexDataVisualizerView: FC = (dataVi useEffect(() => { const timeUpdateSubscription = merge( timefilter.getTimeUpdate$(), - dataVisualizerTimefilterRefresh$ + dataVisualizerRefresh$ ).subscribe(() => { setGlobalState({ time: timefilter.getTime(), @@ -735,13 +735,33 @@ export const IndexDataVisualizerView: FC = (dataVi [currentIndexPattern, searchQueryLanguage, searchString] ); + // Some actions open up fly-out or popup + // This variable is used to keep track of them and clean up when unmounting + const actionFlyoutRef = useRef<() => void | undefined>(); + useEffect(() => { + const ref = actionFlyoutRef; + return () => { + // Clean up any of the flyout/editor opened from the actions + if (ref.current) { + ref.current(); + } + }; + }, []); + // Inject custom action column for the index based visualizer + // Hide the column completely if no access to any of the plugins const extendedColumns = useMemo(() => { - if (lensPlugin === undefined) { - // eslint-disable-next-line no-console - console.error('Lens plugin not available'); - return; - } + const actions = getActions( + currentIndexPattern, + services, + { + searchQueryLanguage, + searchString, + }, + actionFlyoutRef + ); + if (!Array.isArray(actions) || actions.length < 1) return; + const actionColumn: EuiTableActionsColumnType = { name: ( = (dataVi defaultMessage="Actions" /> ), - actions: getActions(currentIndexPattern, lensPlugin, { searchQueryLanguage, searchString }), + actions, width: '100px', }; return [actionColumn]; - }, [currentIndexPattern, lensPlugin, searchQueryLanguage, searchString]); + }, [currentIndexPattern, services, searchQueryLanguage, searchString]); const helpLink = docLinks.links.ml.guide; + return ( @@ -765,10 +786,24 @@ export const IndexDataVisualizerView: FC = (dataVi - -

{currentIndexPattern.title}

-
+
+ +

{currentIndexPattern.title}

+
+ +
+ {currentIndexPattern.timeFieldName !== undefined && ( diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index.ts new file mode 100644 index 00000000000000..c26f84a4c22fc4 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { DataVisualizerIndexPatternManagement } from './index_pattern_management'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx new file mode 100644 index 00000000000000..f3b1e5fce97909 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx @@ -0,0 +1,127 @@ +/* + * 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, { useEffect, useRef, useState } from 'react'; +import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { useDataVisualizerKibana } from '../../../kibana_context'; +import { dataVisualizerRefresh$, Refresh } from '../../services/timefilter_refresh_service'; + +export interface DataVisualizerIndexPatternManagementProps { + /** + * Currently selected index pattern + */ + currentIndexPattern?: IndexPattern; + /** + * Read from the Fields API + */ + useNewFieldsApi?: boolean; +} + +export function DataVisualizerIndexPatternManagement( + props: DataVisualizerIndexPatternManagementProps +) { + const { + services: { indexPatternFieldEditor, application }, + } = useDataVisualizerKibana(); + + const { useNewFieldsApi, currentIndexPattern } = props; + const indexPatternFieldEditPermission = indexPatternFieldEditor?.userPermissions.editIndexPattern(); + const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi; + const [isAddIndexPatternFieldPopoverOpen, setIsAddIndexPatternFieldPopoverOpen] = useState(false); + + const closeFieldEditor = useRef<() => void | undefined>(); + useEffect(() => { + return () => { + // Make sure to close the editor when unmounting + if (closeFieldEditor.current) { + closeFieldEditor.current(); + } + }; + }, []); + + if (indexPatternFieldEditor === undefined || !currentIndexPattern || !canEditIndexPatternField) { + return null; + } + + const addField = () => { + closeFieldEditor.current = indexPatternFieldEditor.openEditor({ + ctx: { + indexPattern: currentIndexPattern, + }, + onSave: () => { + const refresh: Refresh = { + lastRefresh: Date.now(), + }; + dataVisualizerRefresh$.next(refresh); + }, + }); + }; + + return ( + { + setIsAddIndexPatternFieldPopoverOpen(false); + }} + ownFocus + data-test-subj="dataVisualizer-addRuntimeField-popover" + button={ + { + setIsAddIndexPatternFieldPopoverOpen(!isAddIndexPatternFieldPopoverOpen); + }} + /> + } + > + { + setIsAddIndexPatternFieldPopoverOpen(false); + addField(); + }} + > + {i18n.translate('xpack.dataVisualizer.fieldChooser.indexPatterns.addFieldButton', { + defaultMessage: 'Add field to index pattern', + })} + , + { + setIsAddIndexPatternFieldPopoverOpen(false); + application.navigateToApp('management', { + path: `/kibana/indexPatterns/patterns/${props.currentIndexPattern?.id}`, + }); + }} + > + {i18n.translate('xpack.dataVisualizer.fieldChooser.indexPatterns.manageFieldButton', { + defaultMessage: 'Manage index pattern fields', + })} + , + ]} + /> + + ); +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/timefilter_refresh_service.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/timefilter_refresh_service.ts index 49ef9107c3ecea..11f286e7812195 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/timefilter_refresh_service.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/services/timefilter_refresh_service.ts @@ -6,11 +6,10 @@ */ import { Subject } from 'rxjs'; -import { Required } from 'utility-types'; export interface Refresh { lastRefresh: number; timeRange?: { start: string; end: string }; } -export const dataVisualizerTimefilterRefresh$ = new Subject>(); +export const dataVisualizerRefresh$ = new Subject(); diff --git a/x-pack/plugins/data_visualizer/public/application/kibana_context.ts b/x-pack/plugins/data_visualizer/public/application/kibana_context.ts index f7ce13d2fd48d8..58d0ac021ff224 100644 --- a/x-pack/plugins/data_visualizer/public/application/kibana_context.ts +++ b/x-pack/plugins/data_visualizer/public/application/kibana_context.ts @@ -6,8 +6,9 @@ */ import { CoreStart } from 'kibana/public'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { KibanaReactContextValue, useKibana } from '../../../../../src/plugins/kibana_react/public'; import type { DataVisualizerStartDependencies } from '../plugin'; export type StartServices = CoreStart & DataVisualizerStartDependencies; +export type DataVisualizerKibanaReactContextValue = KibanaReactContextValue; export const useDataVisualizerKibana = () => useKibana(); From 63f04342e0d26473d052c7fd092edc24bc786ae3 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 8 Jun 2021 17:41:01 -0500 Subject: [PATCH 02/11] [ML] Add indexPatternField editor plugin as opt dependency --- x-pack/plugins/data_visualizer/kibana.json | 3 ++- .../index_data_visualizer/index_data_visualizer.tsx | 12 +++++++++++- x-pack/plugins/data_visualizer/public/plugin.ts | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/data_visualizer/kibana.json b/x-pack/plugins/data_visualizer/kibana.json index 3934f0ee3417f7..7d4887912ac9e0 100644 --- a/x-pack/plugins/data_visualizer/kibana.json +++ b/x-pack/plugins/data_visualizer/kibana.json @@ -16,7 +16,8 @@ "security", "maps", "home", - "lens" + "lens", + "indexPatternFieldEditor" ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 82a9b93b31a712..f9e9aece48a060 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -178,7 +178,16 @@ export const DataVisualizerUrlStateContextProvider: FC { const coreStart = getCoreStart(); - const { data, maps, embeddable, share, security, fileUpload, lens } = getPluginsStart(); + const { + data, + maps, + embeddable, + share, + security, + fileUpload, + lens, + indexPatternFieldEditor, + } = getPluginsStart(); const services = { data, maps, @@ -187,6 +196,7 @@ export const IndexDataVisualizer: FC = () => { security, fileUpload, lens, + indexPatternFieldEditor, ...coreStart, }; diff --git a/x-pack/plugins/data_visualizer/public/plugin.ts b/x-pack/plugins/data_visualizer/public/plugin.ts index 20d2e93fd68790..477c0b5273e42a 100644 --- a/x-pack/plugins/data_visualizer/public/plugin.ts +++ b/x-pack/plugins/data_visualizer/public/plugin.ts @@ -17,6 +17,7 @@ import type { FileUploadPluginStart } from '../../file_upload/public'; import type { MapsStartApi } from '../../maps/public'; import type { SecurityPluginSetup } from '../../security/public'; import type { LensPublicStart } from '../../lens/public'; +import type { IndexPatternFieldEditorStart } from '../../../../src/plugins/index_pattern_field_editor/public'; import { getFileDataVisualizerComponent, getIndexDataVisualizerComponent } from './api'; import { getMaxBytesFormatted } from './application/common/util/get_max_bytes'; import { registerHomeAddData } from './register_home'; @@ -32,6 +33,7 @@ export interface DataVisualizerStartDependencies { security?: SecurityPluginSetup; share: SharePluginStart; lens?: LensPublicStart; + indexPatternFieldEditor?: IndexPatternFieldEditorStart; } export type DataVisualizerPluginSetup = ReturnType; From e5385bde34f3fe0d6bf92731d16bbf9569a3cdcc Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 8 Jun 2021 17:44:10 -0500 Subject: [PATCH 03/11] [ML] Remove lens from ML's dependency --- x-pack/plugins/ml/kibana.json | 1 - x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts | 2 -- x-pack/plugins/ml/public/application/app.tsx | 1 - .../ml/public/application/contexts/kibana/kibana_context.ts | 2 -- x-pack/plugins/ml/public/plugin.ts | 3 --- x-pack/plugins/ml/tsconfig.json | 3 +-- 6 files changed, 1 insertion(+), 11 deletions(-) diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 92bac61f3fab3e..f06319b3496f38 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -28,7 +28,6 @@ "management", "licenseManagement", "maps", - "lens", "usageCollection" ], "server": true, diff --git a/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts b/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts index 0907cce832bf85..f16ba275246704 100644 --- a/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts +++ b/x-pack/plugins/ml/public/__mocks__/ml_start_deps.ts @@ -9,7 +9,6 @@ import { uiActionsPluginMock } from '../../../../../src/plugins/ui_actions/publi import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { kibanaLegacyPluginMock } from '../../../../../src/plugins/kibana_legacy/public/mocks'; import { embeddablePluginMock } from '../../../../../src/plugins/embeddable/public/mocks'; -import { lensPluginMock } from '../../../lens/public/mocks'; import { triggersActionsUiMock } from '../../../triggers_actions_ui/public/mocks'; export const createMlStartDepsMock = () => ({ @@ -22,7 +21,6 @@ export const createMlStartDepsMock = () => ({ spaces: jest.fn(), embeddable: embeddablePluginMock.createStartContract(), maps: jest.fn(), - lens: lensPluginMock.createStartContract(), triggersActionsUi: triggersActionsUiMock.createStart(), dataVisualizer: jest.fn(), }); diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index f16a7c561ac5d6..40845302a32a07 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -77,7 +77,6 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { data: deps.data, security: deps.security, licenseManagement: deps.licenseManagement, - lens: deps.lens, storage: localStorage, embeddable: deps.embeddable, maps: deps.maps, 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 841f0d03fa21c4..1ade617fa60a52 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 @@ -19,7 +19,6 @@ import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/p import type { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public'; import type { MapsStartApi } from '../../../../../maps/public'; import type { DataVisualizerPluginStart } from '../../../../../data_visualizer/public'; -import type { LensPublicStart } from '../../../../../lens/public'; import { TriggersAndActionsUIPublicPluginStart } from '../../../../../triggers_actions_ui/public'; interface StartPlugins { @@ -29,7 +28,6 @@ interface StartPlugins { share: SharePluginStart; embeddable: EmbeddableStart; maps?: MapsStartApi; - lens?: LensPublicStart; triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; dataVisualizer?: DataVisualizerPluginStart; } diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 42440883408ae5..99b09eda208fc7 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -46,7 +46,6 @@ 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'; -import { LensPublicStart } from '../../lens/public'; import { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, @@ -63,7 +62,6 @@ export interface MlStartDependencies { spaces?: SpacesPluginStart; embeddable: EmbeddableStart; maps?: MapsStartApi; - lens?: LensPublicStart; triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; dataVisualizer: DataVisualizerPluginStart; } @@ -120,7 +118,6 @@ export class MlPlugin implements Plugin { embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, maps: pluginsStart.maps, uiActions: pluginsStart.uiActions, - lens: pluginsStart.lens, kibanaVersion, triggersActionsUi: pluginsStart.triggersActionsUi, dataVisualizer: pluginsStart.dataVisualizer, diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 221718d4233833..8e859c35e3f853 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -16,7 +16,7 @@ "../../../typings/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 "public/**/*.json", - "server/**/*.json", + "server/**/*.json" ], "references": [ { "path": "../../../src/core/tsconfig.json" }, @@ -28,7 +28,6 @@ { "path": "../license_management/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, { "path": "../maps/tsconfig.json" }, - { "path": "../lens/tsconfig.json" }, { "path": "../security/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, { "path": "../alerting/tsconfig.json" }, From a2eed85c7c7844d3761f63b65db31ddb4bac36fb Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Mon, 14 Jun 2021 20:12:00 -0500 Subject: [PATCH 04/11] [ML] Fix custom display name cause field to be missing --- .../data_visualizer_stats_table.tsx | 25 +++++++++++-------- .../stats_table/types/field_vis_config.ts | 2 ++ .../index_data_visualizer_view.tsx | 17 +++++++++---- .../data_loader/data_loader.ts | 6 ++--- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx index afadc5c5ae4a48..5a416e2ca93b12 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx @@ -111,21 +111,22 @@ export const DataVisualizerTable = ({ width: '40px', isExpander: true, render: (item: DataVisualizerTableItem) => { - if (item.fieldName === undefined) return null; - const direction = expandedRowItemIds.includes(item.fieldName) ? 'arrowUp' : 'arrowDown'; + const displayName = item.displayName ?? item.fieldName; + if (displayName === undefined) return null; + const direction = expandedRowItemIds.includes(displayName) ? 'arrowUp' : 'arrowDown'; return ( toggleDetails(item)} aria-label={ - expandedRowItemIds.includes(item.fieldName) + expandedRowItemIds.includes(displayName) ? i18n.translate('xpack.dataVisualizer.dataGrid.rowCollapse', { defaultMessage: 'Hide details for {fieldName}', - values: { fieldName: item.fieldName }, + values: { fieldName: displayName }, }) : i18n.translate('xpack.dataVisualizer.dataGrid.rowExpand', { defaultMessage: 'Show details for {fieldName}', - values: { fieldName: item.fieldName }, + values: { fieldName: displayName }, }) } iconType={direction} @@ -157,11 +158,15 @@ export const DataVisualizerTable = ({ }), sortable: true, truncateText: true, - render: (fieldName: string) => ( - - {fieldName} - - ), + render: (fieldName: string, item: DataVisualizerTableItem) => { + const displayName = item.displayName ?? item.fieldName; + + return ( + + {displayName} + + ); + }, align: LEFT_ALIGNMENT as HorizontalAlignment, 'data-test-subj': 'dataVisualizerTableColumnName', }, diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts index d58497f6cd7cc2..ae78020ae7b7d8 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts @@ -24,6 +24,7 @@ export interface MetricFieldVisStats { export interface FieldVisConfig { type: JobFieldType; fieldName?: string; + displayName?: string; existsInDocs: boolean; aggregatable: boolean; loading: boolean; @@ -35,6 +36,7 @@ export interface FieldVisConfig { export interface FileBasedFieldVisConfig { type: JobFieldType; fieldName?: string; + displayName?: string; stats?: FieldVisStats; format?: string; } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index 831f7044af51fc..b0493630704313 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -533,7 +533,7 @@ export const IndexDataVisualizerView: FC = (dataVi }); const metricExistsFields = allMetricFields.filter((f) => { return aggregatableExistsFields.find((existsF) => { - return existsF.fieldName === f.displayName; + return existsF.fieldName === f.spec.name; }); }); @@ -562,7 +562,7 @@ export const IndexDataVisualizerView: FC = (dataVi metricFieldsToShow.forEach((field) => { const fieldData = aggregatableFields.find((f) => { - return f.fieldName === field.displayName; + return f.fieldName === field.spec.name; }); const metricConfig: FieldVisConfig = { @@ -572,6 +572,9 @@ export const IndexDataVisualizerView: FC = (dataVi loading: true, aggregatable: true, }; + if (field.displayName !== metricConfig.fieldName) { + metricConfig.displayName = field.displayName; + } configs.push(metricConfig); }); @@ -607,7 +610,7 @@ export const IndexDataVisualizerView: FC = (dataVi allNonMetricFields.forEach((f) => { const checkAggregatableField = aggregatableExistsFields.find( - (existsField) => existsField.fieldName === f.displayName + (existsField) => existsField.fieldName === f.spec.name ); if (checkAggregatableField !== undefined) { @@ -615,7 +618,7 @@ export const IndexDataVisualizerView: FC = (dataVi nonMetricFieldData.push(checkAggregatableField); } else { const checkNonAggregatableField = nonAggregatableExistsFields.find( - (existsField) => existsField.fieldName === f.displayName + (existsField) => existsField.fieldName === f.spec.name ); if (checkNonAggregatableField !== undefined) { @@ -643,7 +646,7 @@ export const IndexDataVisualizerView: FC = (dataVi const configs: FieldVisConfig[] = []; nonMetricFieldsToShow.forEach((field) => { - const fieldData = nonMetricFieldData.find((f) => f.fieldName === field.displayName); + const fieldData = nonMetricFieldData.find((f) => f.fieldName === field.spec.name); const nonMetricConfig = { ...fieldData, @@ -665,6 +668,10 @@ export const IndexDataVisualizerView: FC = (dataVi nonMetricConfig.isUnsupportedType = true; } + if (field.displayName !== nonMetricConfig.fieldName) { + nonMetricConfig.displayName = field.displayName; + } + configs.push(nonMetricConfig); }); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts index 3cb0d4d672f485..8a486b0935137e 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts @@ -49,10 +49,10 @@ export class DataLoader { this._indexPattern.fields.forEach((field) => { const fieldName = field.displayName !== undefined ? field.displayName : field.name; if (this.isDisplayField(fieldName) === true) { - if (field.aggregatable === true && field.type !== KBN_FIELD_TYPES.GEO_SHAPE) { - aggregatableFields.push(fieldName); + if (field.aggregatable === true || field.type !== KBN_FIELD_TYPES.GEO_SHAPE) { + aggregatableFields.push(field.name); } else { - nonAggregatableFields.push(fieldName); + nonAggregatableFields.push(field.name); } } }); From 1afeb8a23dfc135738efb4cb5d43f56a650923fc Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Mon, 14 Jun 2021 20:27:59 -0500 Subject: [PATCH 05/11] [ML] Add delete option --- .../field_data_row/action_menu/actions.ts | 29 +++++++++++++++++++ .../stats_table/types/field_vis_config.ts | 1 + .../index_data_visualizer_view.tsx | 2 ++ 3 files changed, 32 insertions(+) diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 187da24f54cbdf..698cb812050c33 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -83,6 +83,35 @@ export function getActions( }, 'data-test-subj': 'dataVisualizerActionEditIndexPatternFieldButton', }); + actions.push({ + name: i18n.translate('xpack.dataVisualizer.index.dataGrid.deleteIndexPatternFieldTitle', { + defaultMessage: 'Delete index pattern field', + }), + description: i18n.translate( + 'xpack.dataVisualizer.index.dataGrid.deleteIndexPatternFieldDescription', + { + defaultMessage: 'Delete index pattern field', + } + ), + type: 'icon', + icon: 'trash', + available: (item: FieldVisConfig) => { + return item.deletable === true; + }, + onClick: (item: FieldVisConfig) => { + actionFlyoutRef.current = indexPatternFieldEditor?.openDeleteModal({ + ctx: { indexPattern }, + fieldName: item.fieldName!, + onDelete: () => { + const refresh: Refresh = { + lastRefresh: Date.now(), + }; + dataVisualizerRefresh$.next(refresh); + }, + }); + }, + 'data-test-subj': 'dataVisualizerActionEditIndexPatternFieldButton', + }); } return actions; } diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts index ae78020ae7b7d8..eeb9fe12692fda 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/types/field_vis_config.ts @@ -31,6 +31,7 @@ export interface FieldVisConfig { stats?: FieldVisStats; fieldFormat?: any; isUnsupportedType?: boolean; + deletable?: boolean; } export interface FileBasedFieldVisConfig { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index b0493630704313..b116b25670ad27 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -571,6 +571,7 @@ export const IndexDataVisualizerView: FC = (dataVi type: JOB_FIELD_TYPES.NUMBER, loading: true, aggregatable: true, + deletable: field.runtimeField !== undefined, }; if (field.displayName !== metricConfig.fieldName) { metricConfig.displayName = field.displayName; @@ -654,6 +655,7 @@ export const IndexDataVisualizerView: FC = (dataVi aggregatable: field.aggregatable, scripted: field.scripted, loading: fieldData.existsInDocs, + deletable: field.runtimeField !== undefined, }; // Map the field type from the Kibana index pattern to the field type From c8004f2148ab1394265edfc1ff7423db0663e0d6 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Mon, 14 Jun 2021 20:35:58 -0500 Subject: [PATCH 06/11] [ML] Fix aggregatableFields logic --- .../index_data_visualizer/data_loader/data_loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts index 8a486b0935137e..468bd3a2bd7ee1 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/data_loader/data_loader.ts @@ -49,7 +49,7 @@ export class DataLoader { this._indexPattern.fields.forEach((field) => { const fieldName = field.displayName !== undefined ? field.displayName : field.name; if (this.isDisplayField(fieldName) === true) { - if (field.aggregatable === true || field.type !== KBN_FIELD_TYPES.GEO_SHAPE) { + if (field.aggregatable === true && field.type !== KBN_FIELD_TYPES.GEO_SHAPE) { aggregatableFields.push(field.name); } else { nonAggregatableFields.push(field.name); From 3a5950f5632b38667b769a5c25ed6c71175287a7 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 15 Jun 2021 17:16:57 -0500 Subject: [PATCH 07/11] [ML] Add functional tests --- .../field_data_row/action_menu/actions.ts | 2 +- .../data_visualizer_stats_table.tsx | 8 +- .../index_pattern_management.tsx | 9 +- .../data_visualizer/file_data_visualizer.ts | 1 + .../apps/ml/data_visualizer/index.ts | 1 + ...ata_visualizer_index_pattern_management.ts | 281 ++++++++++++++++++ ...ata_visualizer_index_pattern_management.ts | 116 ++++++++ .../services/ml/data_visualizer_table.ts | 142 ++++++++- x-pack/test/functional/services/ml/index.ts | 6 + 9 files changed, 552 insertions(+), 14 deletions(-) create mode 100644 x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts create mode 100644 x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 698cb812050c33..0c8cb3d3e2e102 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -110,7 +110,7 @@ export function getActions( }, }); }, - 'data-test-subj': 'dataVisualizerActionEditIndexPatternFieldButton', + 'data-test-subj': 'dataVisualizerActionDeleteIndexPatternFieldButton', }); } return actions; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx index 5a416e2ca93b12..9b769e016cc786 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx @@ -112,14 +112,14 @@ export const DataVisualizerTable = ({ isExpander: true, render: (item: DataVisualizerTableItem) => { const displayName = item.displayName ?? item.fieldName; - if (displayName === undefined) return null; - const direction = expandedRowItemIds.includes(displayName) ? 'arrowUp' : 'arrowDown'; + if (item.fieldName === undefined) return null; + const direction = expandedRowItemIds.includes(item.fieldName) ? 'arrowUp' : 'arrowDown'; return ( toggleDetails(item)} aria-label={ - expandedRowItemIds.includes(displayName) + expandedRowItemIds.includes(item.fieldName) ? i18n.translate('xpack.dataVisualizer.dataGrid.rowCollapse', { defaultMessage: 'Hide details for {fieldName}', values: { fieldName: displayName }, @@ -163,7 +163,7 @@ export const DataVisualizerTable = ({ return ( - {displayName} + {displayName} ); }, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx index f3b1e5fce97909..dae511d128a633 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx @@ -71,12 +71,12 @@ export function DataVisualizerIndexPatternManagement( setIsAddIndexPatternFieldPopoverOpen(false); }} ownFocus - data-test-subj="dataVisualizer-addRuntimeField-popover" + data-test-subj="dataVisualizerIndexPatternManagementPopover" button={ { setIsAddIndexPatternFieldPopoverOpen(false); addField(); @@ -108,7 +109,7 @@ export function DataVisualizerIndexPatternManagement( { setIsAddIndexPatternFieldPopoverOpen(false); application.navigateToApp('management', { 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 a8fed205a9e56a..3867ed6f7dfea0 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 @@ -223,6 +223,7 @@ export default function ({ getService }: FtrProviderContext) { fieldRow.docCountFormatted, fieldRow.topValuesCount, false, + false, false ); } diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index.ts b/x-pack/test/functional/apps/ml/data_visualizer/index.ts index 65f7033b5bd66a..3e6b644a0b494a 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index.ts @@ -13,6 +13,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./index_data_visualizer')); loadTestFile(require.resolve('./index_data_visualizer_actions_panel')); + loadTestFile(require.resolve('./index_data_visualizer_index_pattern_management')); loadTestFile(require.resolve('./file_data_visualizer')); }); } diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts new file mode 100644 index 00000000000000..74d774c043d051 --- /dev/null +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts @@ -0,0 +1,281 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; +import { ML_JOB_FIELD_TYPES } from '../../../../../plugins/ml/common/constants/field_types'; +import { FieldVisConfig } from '../../../../../plugins/data_visualizer/public/application/common/components/stats_table/types'; + +interface MetricFieldVisConfig extends FieldVisConfig { + statsMaxDecimalPlaces: number; + docCountFormatted: string; + topValuesCount: number; + viewableInLens: boolean; + hasActionMenu: boolean; +} + +interface NonMetricFieldVisConfig extends FieldVisConfig { + docCountFormatted: string; + exampleCount: number; + viewableInLens: boolean; + hasActionMenu: boolean; +} + +interface TestData { + suiteTitle: string; + sourceIndexOrSavedSearch: string; + rowsPerPage?: 10 | 25 | 50; + newFields?: Array<{ fieldName: string; type: string; script: string }>; + fieldsToRename?: Array<{ originalName: string; newName: string }>; + expected: { + totalDocCountFormatted: string; + metricFields?: MetricFieldVisConfig[]; + nonMetricFields?: NonMetricFieldVisConfig[]; + visibleMetricFieldsCount: number; + totalMetricFieldsCount: number; + populatedFieldsCount: number; + totalFieldsCount: number; + }; +} + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + const originalTestData: TestData = { + suiteTitle: 'original index pattern', + sourceIndexOrSavedSearch: 'ft_farequote', + expected: { + totalDocCountFormatted: '86,274', + metricFields: [], + nonMetricFields: [], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + }, + }; + const addDeleteFieldTestData: TestData = { + suiteTitle: 'add field', + sourceIndexOrSavedSearch: 'ft_farequote', + newFields: [ + { + fieldName: 'rt_airline_lowercase', + type: 'Keyword', + script: 'emit(params._source.airline.toLowerCase())', + }, + ], + expected: { + totalDocCountFormatted: '86,274', + metricFields: [], + nonMetricFields: [ + { + fieldName: 'rt_airline_lowercase', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 10, + docCountFormatted: '5000 (100%)', + viewableInLens: true, + hasActionMenu: true, + }, + ], + visibleMetricFieldsCount: 2, + totalMetricFieldsCount: 2, + populatedFieldsCount: 9, + totalFieldsCount: 10, + }, + }; + const customLabelTestData: TestData = { + suiteTitle: 'custom label', + sourceIndexOrSavedSearch: 'ft_farequote', + fieldsToRename: [ + { + originalName: 'responsetime', + newName: 'new_responsetime', + }, + ], + expected: { + totalDocCountFormatted: '86,274', + metricFields: [ + { + fieldName: 'new_responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '5000 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 10, + viewableInLens: true, + hasActionMenu: false, + }, + ], + nonMetricFields: [], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + }, + }; + + async function navigateToIndexDataVisualizer(testData: TestData) { + // Start navigation from the base of the ML app. + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the data visualizer selector page` + ); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataVisualizer(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the saved search selection page` + ); + await ml.dataVisualizer.navigateToIndexPatternSelection(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} loads the index data visualizer page` + ); + await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer( + testData.sourceIndexOrSavedSearch + ); + + await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the time range step`); + await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); + + await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); + await ml.dataVisualizerIndexBased.clickUseFullDataButton( + testData.expected.totalDocCountFormatted + ); + } + + async function checkPageDetails(testData: TestData) { + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays elements in the doc count panel correctly` + ); + await ml.dataVisualizerIndexBased.assertTotalDocCountHeaderExist(); + await ml.dataVisualizerIndexBased.assertTotalDocCountChartExist(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays elements in the data visualizer table correctly` + ); + 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(); + await ml.dataVisualizerTable.assertFieldNameInputExists(); + + await ml.dataVisualizerIndexBased.assertFieldCountPanelExist(); + await ml.dataVisualizerIndexBased.assertMetricFieldsSummaryExist(); + await ml.dataVisualizerIndexBased.assertFieldsSummaryExist(); + await ml.dataVisualizerIndexBased.assertVisibleMetricFieldsCount( + testData.expected.visibleMetricFieldsCount + ); + await ml.dataVisualizerIndexBased.assertTotalMetricFieldsCount( + testData.expected.totalMetricFieldsCount + ); + await ml.dataVisualizerIndexBased.assertVisibleFieldsCount( + testData.expected.populatedFieldsCount + ); + await ml.dataVisualizerIndexBased.assertTotalFieldsCount(testData.expected.totalFieldsCount); + } + + describe('index pattern management', function () { + this.tags(['mlqa']); + const indexPatternTitle = 'ft_farequote'; + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + }); + + beforeEach(async () => { + await ml.testResources.createIndexPatternIfNeeded(indexPatternTitle, '@timestamp'); + await navigateToIndexDataVisualizer(originalTestData); + }); + + afterEach(async () => { + await ml.testResources.deleteIndexPatternByTitle(indexPatternTitle); + }); + + it(`adds new field`, async () => { + await ml.testExecution.logTestStep('adds new runtime fields'); + for (const newField of addDeleteFieldTestData.newFields!) { + await ml.dataVisualizerIndexPatternManagement.addRuntimeField( + newField.fieldName, + newField.script, + newField.type + ); + } + + await ml.testExecution.logTestStep('displays details for added runtime metric fields'); + for (const fieldRow of addDeleteFieldTestData.expected.metricFields as Array< + Required + >) { + await ml.dataVisualizerTable.assertNumberFieldContents( + fieldRow.fieldName, + fieldRow.docCountFormatted, + fieldRow.topValuesCount, + fieldRow.viewableInLens, + fieldRow.hasActionMenu + ); + } + await ml.testExecution.logTestStep('displays details for added runtime non metric fields'); + for (const fieldRow of addDeleteFieldTestData.expected.nonMetricFields!) { + await ml.dataVisualizerTable.assertNonMetricFieldContents( + fieldRow.type, + fieldRow.fieldName!, + fieldRow.docCountFormatted, + fieldRow.exampleCount, + fieldRow.viewableInLens, + fieldRow.hasActionMenu + ); + } + await checkPageDetails(addDeleteFieldTestData); + }); + + it(`navigates to index pattern management page`, async () => { + await ml.dataVisualizerIndexPatternManagement.clickIndexPatternManagementButton(); + await ml.dataVisualizerIndexPatternManagement.clickManageIndexPatternAction(); + }); + + it(`sets custom label for existing field`, async () => { + for (const field of customLabelTestData.fieldsToRename!) { + await ml.dataVisualizerIndexPatternManagement.renameField( + field.originalName, + field.newName + ); + await ml.dataVisualizerTable.assertDisplayName(field.originalName, field.newName); + } + }); + + it(`deletes existing field`, async () => { + await ml.testExecution.logTestStep('adds new runtime fields'); + for (const newField of addDeleteFieldTestData.newFields!) { + await ml.dataVisualizerIndexPatternManagement.clickIndexPatternManagementButton(); + await ml.dataVisualizerIndexPatternManagement.clickAddIndexPatternFieldAction(); + await ml.dataVisualizerIndexPatternManagement.addRuntimeField( + newField.fieldName, + newField.script, + newField.type + ); + } + await ml.testExecution.logTestStep('deletes newly added runtime fields'); + for (const fieldToDelete of addDeleteFieldTestData.newFields!) { + await ml.dataVisualizerIndexPatternManagement.deleteField(fieldToDelete.fieldName); + } + + await ml.testExecution.logTestStep('displays page details without the deleted fields'); + await checkPageDetails(originalTestData); + }); + }); +} diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts b/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts new file mode 100644 index 00000000000000..84e13eac579bb1 --- /dev/null +++ b/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts @@ -0,0 +1,116 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { MlDataVisualizerTable } from './data_visualizer_table'; + +export function MachineLearningDataVisualizerIndexPatternManagementProvider( + { getService }: FtrProviderContext, + dataVisualizerTable: MlDataVisualizerTable +) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const find = getService('find'); + const fieldEditor = getService('fieldEditor'); + const comboBox = getService('comboBox'); + + return { + async assertIndexPatternManagementButtonExists() { + await testSubjects.existOrFail('dataVisualizerIndexPatternManagementButton'); + }, + async assertIndexPatternManagementMenuExists() { + await testSubjects.existOrFail('dataVisualizerIndexPatternManagementMenu'); + }, + async assertIndexPatternFieldEditorExists() { + await find.byClassName('indexPatternFieldEditor__form'); + }, + + async clickIndexPatternManagementButton() { + await retry.tryForTime(5000, async () => { + await testSubjects.clickWhenNotDisabled('dataVisualizerIndexPatternManagementButton'); + await this.assertIndexPatternManagementMenuExists(); + }); + }, + async clickAddIndexPatternFieldAction() { + await retry.tryForTime(5000, async () => { + await this.assertIndexPatternManagementMenuExists(); + await testSubjects.clickWhenNotDisabled('dataVisualizerAddIndexPatternFieldAction'); + await this.assertIndexPatternFieldEditorExists(); + }); + }, + + async clickManageIndexPatternAction() { + await retry.tryForTime(5000, async () => { + await this.assertIndexPatternManagementMenuExists(); + await testSubjects.clickWhenNotDisabled('dataVisualizerManageIndexPatternAction'); + await testSubjects.existOrFail('editIndexPattern'); + }); + }, + + async assertIndexPatternFieldEditorFieldType(expectedIdentifier: string) { + await retry.tryForTime(2000, async () => { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'typeField > comboBoxInput' + ); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier === '' ? [] : [expectedIdentifier], + `Expected type field to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); + }); + }, + + async setIndexPatternFieldEditorFieldType(type: string) { + await comboBox.set('typeField > comboBoxInput', type); + + await this.assertIndexPatternFieldEditorFieldType(type); + }, + + async addRuntimeField(name: string, script: string, fieldType: string) { + await this.clickIndexPatternManagementButton(); + await this.clickAddIndexPatternFieldAction(); + + await retry.tryForTime(5000, async () => { + await this.assertIndexPatternFieldEditorExists(); + + await fieldEditor.setName(name); + + await fieldEditor.enableValue(); + await fieldEditor.typeScript(script); + + await this.setIndexPatternFieldEditorFieldType(fieldType); + + await fieldEditor.save(); + }); + }, + + async renameField(originalName: string, newName: string) { + await retry.tryForTime(5000, async () => { + await dataVisualizerTable.clickEditIndexPatternFieldButton(originalName); + await this.assertIndexPatternFieldEditorExists(); + await fieldEditor.enableCustomLabel(); + await fieldEditor.setCustomLabel(newName); + await fieldEditor.save(); + }); + }, + + async confirmDeleteField() { + await testSubjects.existOrFail('deleteModalConfirmText'); + await testSubjects.setValue('deleteModalConfirmText', 'remove'); + await testSubjects.click('confirmModalConfirmButton'); + await testSubjects.missingOrFail('deleteModalConfirmText'); + }, + + async deleteField(fieldName: string) { + await retry.tryForTime(5000, async () => { + await dataVisualizerTable.clickActionMenuDeleteIndexPatternFieldButton(fieldName); + await this.confirmDeleteField(); + await dataVisualizerTable.assertRowNotExists(fieldName); + }); + }, + }; +} 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 1eb0edbe01c8ef..9dd61f5a14fc60 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -18,6 +18,7 @@ export function MachineLearningDataVisualizerTableProvider( ) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); + const find = getService('find'); return new (class DataVisualizerTable { public async parseDataVisualizerTable() { @@ -79,6 +80,25 @@ export function MachineLearningDataVisualizerTableProvider( await testSubjects.existOrFail(this.rowSelector(fieldName)); } + public async assertRowNotExists(fieldName: string) { + await retry.tryForTime(1000, async () => { + await testSubjects.missingOrFail(this.rowSelector(fieldName)); + }); + } + + public async assertDisplayName(fieldName: string, expectedDisplayName: string) { + await retry.tryForTime(10000, async () => { + const subj = await testSubjects.find( + this.rowSelector(fieldName, `dataVisualizerDisplayName-${fieldName}`) + ); + const displayName = await subj.getVisibleText(); + expect(displayName).to.eql( + expectedDisplayName, + `Expected display name of ${fieldName} to be '${expectedDisplayName}' (got '${displayName}')` + ); + }); + } + public detailsSelector(fieldName: string, subSelector?: string) { const row = `~dataVisualizerTable > ~dataVisualizerFieldExpandedRow-${fieldName}`; return !subSelector ? row : `${row} > ${subSelector}`; @@ -133,10 +153,85 @@ export function MachineLearningDataVisualizerTableProvider( ); } - public async assertViewInLensActionEnabled(fieldName: string) { + public async ensureActionsMenuOpen(fieldName: string) { + await retry.tryForTime(30 * 1000, async () => { + const panel = await find.existsByCssSelector('euiContextMenuPanel'); + if (!panel) { + await testSubjects.click(this.rowSelector(fieldName, 'euiCollapsedItemActionsButton')); + await find.existsByCssSelector('euiContextMenuPanel'); + } + }); + } + + public async ensureActionsMenuClosed(fieldName: string, action: string) { + await retry.tryForTime(30 * 1000, async () => { + if (await testSubjects.exists(action)) { + await testSubjects.click(this.rowSelector(fieldName, 'euiCollapsedItemActionsButton')); + await testSubjects.missingOrFail(action, { timeout: 5000 }); + } + }); + } + + public async assertActionMenuViewInLensEnabled(fieldName: string, expectedValue: boolean) { + await retry.tryForTime(30 * 1000, async () => { + await this.ensureActionsMenuOpen(fieldName); + const actionMenuViewInLensButton = await find.byCssSelector( + '[data-test-subj="dataVisualizerActionViewInLensButton"][class="euiContextMenuItem"]' + ); + const isEnabled = await actionMenuViewInLensButton.isEnabled(); + expect(isEnabled).to.eql( + expectedValue, + `Expected "Explore in lens" action menu button for '${fieldName}' to be '${ + expectedValue ? 'enabled' : 'disabled' + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` + ); + }); + } + + public async assertActionMenuDeleteIndexPatternFieldButtonEnabled( + fieldName: string, + expectedValue: boolean + ) { + await this.ensureActionsMenuOpen(fieldName); + const actionMenuViewInLensButton = await find.byCssSelector( + '[data-test-subj="dataVisualizerActionDeleteIndexPatternFieldButton"][class="euiContextMenuItem"]' + ); + const isEnabled = await actionMenuViewInLensButton.isEnabled(); + expect(isEnabled).to.eql( + expectedValue, + `Expected "Delete index pattern field" action menu button for '${fieldName}' to be '${ + expectedValue ? 'enabled' : 'disabled' + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` + ); + } + public async clickActionMenuDeleteIndexPatternFieldButton(fieldName: string) { + const testSubj = 'dataVisualizerActionDeleteIndexPatternFieldButton'; + await retry.tryForTime(5000, async () => { + await this.ensureActionsMenuOpen(fieldName); + + const button = await find.byCssSelector( + '[data-test-subj="dataVisualizerActionDeleteIndexPatternFieldButton"][class="euiContextMenuItem"]' + ); + await button.click(); + await this.ensureActionsMenuClosed(fieldName, testSubj); + }); + } + + public async assertActionMenuExists(fieldName: string) { + const actionButton = this.rowSelector(fieldName, 'euiCollapsedItemActionsButton'); + await testSubjects.existOrFail(actionButton); + } + + public async assertViewInLensActionEnabled(fieldName: string, expectedValue: boolean) { const actionButton = this.rowSelector(fieldName, 'dataVisualizerActionViewInLensButton'); await testSubjects.existOrFail(actionButton); - await testSubjects.isEnabled(actionButton); + const isEnabled = await testSubjects.isEnabled(actionButton); + expect(isEnabled).to.eql( + expectedValue, + `Expected "Edit index pattern" button for '${fieldName}' to be '${ + expectedValue ? 'enabled' : 'disabled' + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` + ); } public async assertViewInLensActionNotExists(fieldName: string) { @@ -144,6 +239,33 @@ export function MachineLearningDataVisualizerTableProvider( await testSubjects.missingOrFail(actionButton); } + public async assertEditIndexPatternFieldButtonEnabled( + fieldName: string, + expectedValue: boolean + ) { + const selector = this.rowSelector( + fieldName, + 'dataVisualizerActionEditIndexPatternFieldButton' + ); + await testSubjects.existOrFail(selector); + const isEnabled = await testSubjects.isEnabled(selector); + expect(isEnabled).to.eql( + expectedValue, + `Expected "Edit index pattern" button for '${fieldName}' to be '${ + expectedValue ? 'enabled' : 'disabled' + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` + ); + } + + public async clickEditIndexPatternFieldButton(fieldName: string) { + await retry.tryForTime(5000, async () => { + await this.assertEditIndexPatternFieldButtonEnabled(fieldName, true); + await testSubjects.click( + this.rowSelector(fieldName, 'dataVisualizerActionEditIndexPatternFieldButton') + ); + }); + } + public async assertFieldDistinctValuesExist(fieldName: string) { const selector = this.rowSelector(fieldName, 'dataVisualizerTableColumnDistinctValues'); await testSubjects.existOrFail(selector); @@ -263,6 +385,7 @@ export function MachineLearningDataVisualizerTableProvider( docCountFormatted: string, topValuesCount: number, viewableInLens: boolean, + hasActionMenu = false, checkDistributionPreviewExist = true ) { await this.assertRowExists(fieldName); @@ -282,7 +405,11 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertDistributionPreviewExist(fieldName); } if (viewableInLens) { - await this.assertViewInLensActionEnabled(fieldName); + if (hasActionMenu) { + await this.assertActionMenuViewInLensEnabled(fieldName, true); + } else { + await this.assertViewInLensActionEnabled(fieldName, true); + } } else { await this.assertViewInLensActionNotExists(fieldName); } @@ -378,7 +505,8 @@ export function MachineLearningDataVisualizerTableProvider( fieldName: string, docCountFormatted: string, exampleCount: number, - viewableInLens: boolean + viewableInLens: boolean, + hasActionMenu?: boolean ) { // Currently the data used in the data visualizer tests only contains these field types. if (fieldType === ML_JOB_FIELD_TYPES.DATE) { @@ -394,7 +522,11 @@ export function MachineLearningDataVisualizerTableProvider( } if (viewableInLens) { - await this.assertViewInLensActionEnabled(fieldName); + if (hasActionMenu) { + await this.assertActionMenuViewInLensEnabled(fieldName, true); + } else { + await this.assertViewInLensActionEnabled(fieldName, true); + } } else { await this.assertViewInLensActionNotExists(fieldName); } diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index 64298bbdedd63c..2cc9a3afa442b3 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -23,6 +23,7 @@ import { MachineLearningDataFrameAnalyticsTableProvider } from './data_frame_ana import { MachineLearningDataVisualizerProvider } from './data_visualizer'; import { MachineLearningDataVisualizerFileBasedProvider } from './data_visualizer_file_based'; import { MachineLearningDataVisualizerIndexBasedProvider } from './data_visualizer_index_based'; +import { MachineLearningDataVisualizerIndexPatternManagementProvider } from './data_visualizer_index_pattern_management'; import { MachineLearningJobManagementProvider } from './job_management'; import { MachineLearningJobSelectionProvider } from './job_selection'; import { MachineLearningJobSourceSelectionProvider } from './job_source_selection'; @@ -86,6 +87,10 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataVisualizerFileBased = MachineLearningDataVisualizerFileBasedProvider(context, commonUI); const dataVisualizerIndexBased = MachineLearningDataVisualizerIndexBasedProvider(context); + const dataVisualizerIndexPatternManagement = MachineLearningDataVisualizerIndexPatternManagementProvider( + context, + dataVisualizerTable + ); const jobManagement = MachineLearningJobManagementProvider(context, api); const jobSelection = MachineLearningJobSelectionProvider(context); @@ -131,6 +136,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { dataVisualizer, dataVisualizerFileBased, dataVisualizerIndexBased, + dataVisualizerIndexPatternManagement, dataVisualizerTable, jobManagement, jobSelection, From 4c74b9b6dd90610f0a5b1979abf03994ad49f080 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 16 Jun 2021 08:07:12 -0500 Subject: [PATCH 08/11] [ML] Fix labels & consolidate addRuntimeFields --- .../index_pattern_management/index_pattern_management.tsx | 6 +++--- .../index_data_visualizer_index_pattern_management.ts | 7 ------- .../ml/data_visualizer_index_pattern_management.ts | 3 --- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx index dae511d128a633..cb81640f328c58 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_pattern_management/index_pattern_management.tsx @@ -78,7 +78,7 @@ export function DataVisualizerIndexPatternManagement( iconType="boxesHorizontal" data-test-subj="dataVisualizerIndexPatternManagementButton" aria-label={i18n.translate( - 'xpack.dataVisualizer.fieldChooser.indexPatterns.actionsPopoverLabel', + 'xpack.dataVisualizer.index.indexPatternManagement.actionsPopoverLabel', { defaultMessage: 'Index pattern settings', } @@ -102,7 +102,7 @@ export function DataVisualizerIndexPatternManagement( addField(); }} > - {i18n.translate('xpack.dataVisualizer.fieldChooser.indexPatterns.addFieldButton', { + {i18n.translate('xpack.dataVisualizer.index.indexPatternManagement.addFieldButton', { defaultMessage: 'Add field to index pattern', })} , @@ -117,7 +117,7 @@ export function DataVisualizerIndexPatternManagement( }); }} > - {i18n.translate('xpack.dataVisualizer.fieldChooser.indexPatterns.manageFieldButton', { + {i18n.translate('xpack.dataVisualizer.index.indexPatternManagement.manageFieldButton', { defaultMessage: 'Manage index pattern fields', })} , diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts index 74d774c043d051..0d9163a872043a 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_index_pattern_management.ts @@ -243,11 +243,6 @@ export default function ({ getService }: FtrProviderContext) { await checkPageDetails(addDeleteFieldTestData); }); - it(`navigates to index pattern management page`, async () => { - await ml.dataVisualizerIndexPatternManagement.clickIndexPatternManagementButton(); - await ml.dataVisualizerIndexPatternManagement.clickManageIndexPatternAction(); - }); - it(`sets custom label for existing field`, async () => { for (const field of customLabelTestData.fieldsToRename!) { await ml.dataVisualizerIndexPatternManagement.renameField( @@ -261,8 +256,6 @@ export default function ({ getService }: FtrProviderContext) { it(`deletes existing field`, async () => { await ml.testExecution.logTestStep('adds new runtime fields'); for (const newField of addDeleteFieldTestData.newFields!) { - await ml.dataVisualizerIndexPatternManagement.clickIndexPatternManagementButton(); - await ml.dataVisualizerIndexPatternManagement.clickAddIndexPatternFieldAction(); await ml.dataVisualizerIndexPatternManagement.addRuntimeField( newField.fieldName, newField.script, diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts b/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts index 84e13eac579bb1..f5ea2ef13130a2 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts @@ -76,12 +76,9 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( await retry.tryForTime(5000, async () => { await this.assertIndexPatternFieldEditorExists(); - await fieldEditor.setName(name); - await fieldEditor.enableValue(); await fieldEditor.typeScript(script); - await this.setIndexPatternFieldEditorFieldType(fieldType); await fieldEditor.save(); From 40505a3ba70a08d133635186052d5c7932e12df7 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Wed, 16 Jun 2021 17:18:42 -0500 Subject: [PATCH 09/11] [ML] Add tooltip to show or hide distributions --- .../data_visualizer_stats_table.tsx | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx index 9b769e016cc786..02e4e29dcc05e3 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx @@ -15,6 +15,7 @@ import { EuiIcon, EuiInMemoryTable, EuiText, + EuiToolTip, HorizontalAlignment, LEFT_ALIGNMENT, RIGHT_ALIGNMENT, @@ -199,18 +200,33 @@ export const DataVisualizerTable = ({ {i18n.translate('xpack.dataVisualizer.dataGrid.distributionsColumnName', { defaultMessage: 'Distributions', })} - toggleShowDistribution()} - aria-label={i18n.translate( - 'xpack.dataVisualizer.dataGrid.showDistributionsAriaLabel', - { - defaultMessage: 'Show distributions', + + toggleShowDistribution()} + aria-label={ + !showDistributions + ? i18n.translate('xpack.dataVisualizer.dataGrid.showDistributionsAriaLabel', { + defaultMessage: 'Show distributions', + }) + : i18n.translate('xpack.dataVisualizer.dataGrid.hideDistributionsAriaLabel', { + defaultMessage: 'Hide distributions', + }) } - )} - /> + /> + ), render: (item: DataVisualizerTableItem) => { From dcd6cfaa9c0c39eb882cbcb147c3bc68e695de51 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 22 Jun 2021 15:52:36 -0500 Subject: [PATCH 10/11] Consolidate refreshPage --- .../field_data_row/action_menu/actions.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 0c8cb3d3e2e102..a77ca1d5893497 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -28,6 +28,12 @@ export function getActions( const actions: Array> = []; + const refreshPage = () => { + const refresh: Refresh = { + lastRefresh: Date.now(), + }; + dataVisualizerRefresh$.next(refresh); + }; // Navigate to Lens with prefilled chart for data field if (lensPlugin !== undefined) { const canUseLensEditor = lensPlugin?.canUseEditor(); @@ -73,12 +79,7 @@ export function getActions( actionFlyoutRef.current = indexPatternFieldEditor?.openEditor({ ctx: { indexPattern }, fieldName: item.fieldName, - onSave: () => { - const refresh: Refresh = { - lastRefresh: Date.now(), - }; - dataVisualizerRefresh$.next(refresh); - }, + onSave: refreshPage, }); }, 'data-test-subj': 'dataVisualizerActionEditIndexPatternFieldButton', @@ -102,12 +103,7 @@ export function getActions( actionFlyoutRef.current = indexPatternFieldEditor?.openDeleteModal({ ctx: { indexPattern }, fieldName: item.fieldName!, - onDelete: () => { - const refresh: Refresh = { - lastRefresh: Date.now(), - }; - dataVisualizerRefresh$.next(refresh); - }, + onDelete: refreshPage, }); }, 'data-test-subj': 'dataVisualizerActionDeleteIndexPatternFieldButton', From 6c51e92cd5f9ce63fd4b5bcefb38a46e0b7f633e Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Tue, 22 Jun 2021 15:58:10 -0500 Subject: [PATCH 11/11] [ML] Fix tests --- .../components/field_editor/field_editor.tsx | 6 ++- ...ata_visualizer_index_pattern_management.ts | 15 +++++--- .../services/ml/data_visualizer_table.ts | 38 ++++++++++--------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx index fc25879b128ec0..77ef0903bc6fcd 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/field_editor/field_editor.tsx @@ -216,7 +216,11 @@ const FieldEditorComponent = ({ Boolean(field?.type) && field?.type !== (updatedType && updatedType[0].value); return ( -
+ {/* Name */} diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts b/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts index f5ea2ef13130a2..e5d884b22514b3 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_pattern_management.ts @@ -15,7 +15,6 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( ) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const find = getService('find'); const fieldEditor = getService('fieldEditor'); const comboBox = getService('comboBox'); @@ -27,7 +26,11 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( await testSubjects.existOrFail('dataVisualizerIndexPatternManagementMenu'); }, async assertIndexPatternFieldEditorExists() { - await find.byClassName('indexPatternFieldEditor__form'); + await testSubjects.existOrFail('indexPatternFieldEditorForm'); + }, + + async assertIndexPatternFieldEditorNotExist() { + await testSubjects.missingOrFail('indexPatternFieldEditorForm'); }, async clickIndexPatternManagementButton() { @@ -71,10 +74,10 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( }, async addRuntimeField(name: string, script: string, fieldType: string) { - await this.clickIndexPatternManagementButton(); - await this.clickAddIndexPatternFieldAction(); - await retry.tryForTime(5000, async () => { + await this.clickIndexPatternManagementButton(); + await this.clickAddIndexPatternFieldAction(); + await this.assertIndexPatternFieldEditorExists(); await fieldEditor.setName(name); await fieldEditor.enableValue(); @@ -82,6 +85,7 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( await this.setIndexPatternFieldEditorFieldType(fieldType); await fieldEditor.save(); + await this.assertIndexPatternFieldEditorNotExist(); }); }, @@ -92,6 +96,7 @@ export function MachineLearningDataVisualizerIndexPatternManagementProvider( await fieldEditor.enableCustomLabel(); await fieldEditor.setCustomLabel(newName); await fieldEditor.save(); + await this.assertIndexPatternFieldEditorNotExist(); }); }, 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 9dd61f5a14fc60..2f67a9b75e3d6f 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -19,6 +19,7 @@ export function MachineLearningDataVisualizerTableProvider( const retry = getService('retry'); const testSubjects = getService('testSubjects'); const find = getService('find'); + const browser = getService('browser'); return new (class DataVisualizerTable { public async parseDataVisualizerTable() { @@ -153,22 +154,25 @@ export function MachineLearningDataVisualizerTableProvider( ); } + public async ensureAllMenuPopoversClosed() { + await retry.tryForTime(5000, async () => { + await browser.pressKeys(browser.keys.ESCAPE); + const popoverExists = await find.existsByCssSelector('euiContextMenuPanel'); + expect(popoverExists).to.eql(false, 'All popovers should be closed'); + }); + } + public async ensureActionsMenuOpen(fieldName: string) { await retry.tryForTime(30 * 1000, async () => { - const panel = await find.existsByCssSelector('euiContextMenuPanel'); - if (!panel) { - await testSubjects.click(this.rowSelector(fieldName, 'euiCollapsedItemActionsButton')); - await find.existsByCssSelector('euiContextMenuPanel'); - } + await this.ensureAllMenuPopoversClosed(); + await testSubjects.click(this.rowSelector(fieldName, 'euiCollapsedItemActionsButton')); + await find.existsByCssSelector('euiContextMenuPanel'); }); } - public async ensureActionsMenuClosed(fieldName: string, action: string) { + public async assertActionsMenuClosed(fieldName: string, action: string) { await retry.tryForTime(30 * 1000, async () => { - if (await testSubjects.exists(action)) { - await testSubjects.click(this.rowSelector(fieldName, 'euiCollapsedItemActionsButton')); - await testSubjects.missingOrFail(action, { timeout: 5000 }); - } + await testSubjects.missingOrFail(action, { timeout: 5000 }); }); } @@ -204,31 +208,28 @@ export function MachineLearningDataVisualizerTableProvider( }' (got '${isEnabled ? 'enabled' : 'disabled'}')` ); } + public async clickActionMenuDeleteIndexPatternFieldButton(fieldName: string) { const testSubj = 'dataVisualizerActionDeleteIndexPatternFieldButton'; await retry.tryForTime(5000, async () => { await this.ensureActionsMenuOpen(fieldName); const button = await find.byCssSelector( - '[data-test-subj="dataVisualizerActionDeleteIndexPatternFieldButton"][class="euiContextMenuItem"]' + `[data-test-subj="${testSubj}"][class="euiContextMenuItem"]` ); await button.click(); - await this.ensureActionsMenuClosed(fieldName, testSubj); + await this.assertActionsMenuClosed(fieldName, testSubj); + await testSubjects.existOrFail('runtimeFieldDeleteConfirmModal'); }); } - public async assertActionMenuExists(fieldName: string) { - const actionButton = this.rowSelector(fieldName, 'euiCollapsedItemActionsButton'); - await testSubjects.existOrFail(actionButton); - } - public async assertViewInLensActionEnabled(fieldName: string, expectedValue: boolean) { const actionButton = this.rowSelector(fieldName, 'dataVisualizerActionViewInLensButton'); await testSubjects.existOrFail(actionButton); const isEnabled = await testSubjects.isEnabled(actionButton); expect(isEnabled).to.eql( expectedValue, - `Expected "Edit index pattern" button for '${fieldName}' to be '${ + `Expected "Explore in lens" button for '${fieldName}' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${isEnabled ? 'enabled' : 'disabled'}')` ); @@ -263,6 +264,7 @@ export function MachineLearningDataVisualizerTableProvider( await testSubjects.click( this.rowSelector(fieldName, 'dataVisualizerActionEditIndexPatternFieldButton') ); + await testSubjects.existOrFail('indexPatternFieldEditorForm'); }); }