From f2c3e5654625ec35be4fe1c945a1103bad757555 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 18 Sep 2020 14:20:38 +0300 Subject: [PATCH 01/15] Create lens action and unregister the visualize one --- src/plugins/ui_actions/public/index.ts | 1 + src/plugins/ui_actions/public/types.ts | 2 ++ .../public/actions/visualize_field_action.ts | 1 + x-pack/plugins/lens/kibana.json | 3 +- x-pack/plugins/lens/public/plugin.ts | 14 ++++++++- .../visualize_field_actions.ts | 31 +++++++++++++++++++ 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 476ca0ec170666..664ea5300bfbfd 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -56,6 +56,7 @@ export { VisualizeFieldContext, ACTION_VISUALIZE_FIELD, ACTION_VISUALIZE_GEO_FIELD, + ACTION_VISUALIZE_LENS_FIELD, } from './types'; export { ActionByType, diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index b00f4628ffb967..0be3c19fc1c4d1 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -57,10 +57,12 @@ export interface TriggerContextMapping { const DEFAULT_ACTION = ''; export const ACTION_VISUALIZE_FIELD = 'ACTION_VISUALIZE_FIELD'; export const ACTION_VISUALIZE_GEO_FIELD = 'ACTION_VISUALIZE_GEO_FIELD'; +export const ACTION_VISUALIZE_LENS_FIELD = 'ACTION_VISUALIZE_LENS_FIELD'; export type ActionType = keyof ActionContextMapping; export interface ActionContextMapping { [DEFAULT_ACTION]: BaseContext; [ACTION_VISUALIZE_FIELD]: VisualizeFieldContext; [ACTION_VISUALIZE_GEO_FIELD]: VisualizeFieldContext; + [ACTION_VISUALIZE_LENS_FIELD]: VisualizeFieldContext; } diff --git a/src/plugins/visualize/public/actions/visualize_field_action.ts b/src/plugins/visualize/public/actions/visualize_field_action.ts index 6671d2c9819106..e570ed5e49e6a7 100644 --- a/src/plugins/visualize/public/actions/visualize_field_action.ts +++ b/src/plugins/visualize/public/actions/visualize_field_action.ts @@ -34,6 +34,7 @@ import { AGGS_TERMS_SIZE_SETTING } from '../../common/constants'; export const visualizeFieldAction = createAction({ type: ACTION_VISUALIZE_FIELD, + id: ACTION_VISUALIZE_FIELD, getDisplayName: () => i18n.translate('visualize.discover.visualizeFieldLabel', { defaultMessage: 'Visualize field', diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 67d9d5ef644834..518a91da2c7aaa 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -11,7 +11,8 @@ "urlForwarding", "visualizations", "dashboard", - "charts" + "charts", + "uiActions" ], "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions"], "configPath": ["xpack", "lens"], diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index f9c63f54d67131..06fea82d727c10 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -27,10 +27,15 @@ import { PieVisualization, PieVisualizationPluginSetupPlugins } from './pie_visu import { stopReportManager } from './lens_ui_telemetry'; import { AppNavLinkStatus } from '../../../../src/core/public'; -import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { + UiActionsStart, + ACTION_VISUALIZE_FIELD, + VISUALIZE_FIELD_TRIGGER, +} from '../../../../src/plugins/ui_actions/public'; import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; +import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; import './index.scss'; @@ -111,6 +116,7 @@ export class LensPlugin { title: NOT_INTERNATIONALIZED_PRODUCT_NAME, navLinkStatus: AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { + console.dir(params); const { mountApp } = await import('./app_plugin/mounter'); return mountApp(core, params, this.createEditorFrame!); }, @@ -121,6 +127,12 @@ export class LensPlugin { start(core: CoreStart, startDependencies: LensPluginStartDependencies) { this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; + // unregisters the Visualize action and registers the lens one + startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); + startDependencies.uiActions.addTriggerAction( + VISUALIZE_FIELD_TRIGGER, + visualizeFieldAction(core.application) + ); } stop() { diff --git a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts new file mode 100644 index 00000000000000..f666d3cd92ca91 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts @@ -0,0 +1,31 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + createAction, + ACTION_VISUALIZE_LENS_FIELD, + VisualizeFieldContext, +} from '../../../../../src/plugins/ui_actions/public'; +import { ApplicationStart } from '../../../../../src/core/public'; + +export const visualizeFieldAction = (application: ApplicationStart) => + createAction({ + type: ACTION_VISUALIZE_LENS_FIELD, + id: ACTION_VISUALIZE_LENS_FIELD, + getDisplayName: () => + i18n.translate('visualize.discover.visualizeFieldLabel', { + defaultMessage: 'Visualize field', + }), + isCompatible: async () => !!application.capabilities.visualize.show, + execute: async (context: VisualizeFieldContext) => { + application.navigateToApp('lens', { + state: { + fieldName: context.fieldName, + indexPatternId: context.indexPatternId, + }, + }); + }, + }); From 278e51ba555810ef67b23ceab2bac10d583a5cde Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 18 Sep 2020 14:21:22 +0300 Subject: [PATCH 02/15] remove console --- x-pack/plugins/lens/public/plugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 06fea82d727c10..4e62371785f43e 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -116,7 +116,6 @@ export class LensPlugin { title: NOT_INTERNATIONALIZED_PRODUCT_NAME, navLinkStatus: AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { - console.dir(params); const { mountApp } = await import('./app_plugin/mounter'); return mountApp(core, params, this.createEditorFrame!); }, From a8a7591e9d33aab8544161ca9bfa2fc77f237f72 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 22 Sep 2020 18:19:31 +0300 Subject: [PATCH 03/15] Implement Discover to Lens, wip, missing tests --- .../lens/public/app_plugin/app.test.tsx | 4 ++ x-pack/plugins/lens/public/app_plugin/app.tsx | 4 ++ .../lens/public/app_plugin/mounter.tsx | 6 +- .../editor_frame/editor_frame.test.tsx | 33 ++++++++- .../editor_frame/editor_frame.tsx | 11 ++- .../editor_frame/state_helpers.ts | 10 ++- .../editor_frame/suggestion_helpers.test.ts | 69 +++++++++++++++++++ .../editor_frame/suggestion_helpers.ts | 15 +++- .../workspace_panel/workspace_panel.tsx | 33 ++++++++- .../public/editor_frame_service/mocks.tsx | 1 + .../editor_frame_service/service.test.tsx | 4 ++ .../public/editor_frame_service/service.tsx | 13 +++- .../indexpattern_datasource/indexpattern.tsx | 7 +- .../indexpattern_suggestions.ts | 27 +++++++- .../indexpattern_datasource/loader.test.ts | 28 ++++++++ .../public/indexpattern_datasource/loader.ts | 9 ++- .../visualize_field_actions.ts | 7 +- x-pack/plugins/lens/public/types.ts | 13 +++- 18 files changed, 270 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 63c2a6b9b2f292..9477f2f985e861 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -26,6 +26,7 @@ import { IIndexPattern, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; +import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; const dataStartMock = dataPluginMock.createStartContract(); @@ -131,6 +132,7 @@ describe('Lens App', () => { core: typeof core; storage: Storage; docId?: string; + locationState?: VisualizeFieldContext; docStorage: SavedObjectStore; redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; originatingApp: string | undefined; @@ -185,6 +187,7 @@ describe('Lens App', () => { core: typeof core; storage: Storage; docId?: string; + locationState?: VisualizeFieldContext; docStorage: SavedObjectStore; redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; originatingApp: string | undefined; @@ -241,6 +244,7 @@ describe('Lens App', () => { }, "savedQuery": undefined, "showNoDataPopover": [Function], + "visualizeTriggerFieldContext": undefined, }, ], ] diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index bfdf4ceaaabd32..2ef2f6f172b9c5 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -40,6 +40,7 @@ import { SavedQuery, } from '../../../../../src/plugins/data/public'; import { getFullPath } from '../../common'; +import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; interface State { indicateNoData: boolean; @@ -75,6 +76,7 @@ export function App({ setHeaderActionMenu, history, getAppNameFromId, + locationState, }: { editorFrame: EditorFrameInstance; data: DataPublicPluginStart; @@ -89,6 +91,7 @@ export function App({ setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; history: History; getAppNameFromId?: (appId: string) => string | undefined; + locationState?: VisualizeFieldContext; }) { const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); @@ -541,6 +544,7 @@ export function App({ filters: state.filters, savedQuery: state.savedQuery, doc: state.persistedDoc, + visualizeTriggerFieldContext: locationState, onError, showNoDataPopover, onChange: ({ filterableIndexPatterns, doc, isSaveable }) => { diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index ebc38e4929f6c9..eafe38d210e261 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -12,7 +12,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; - +import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry'; import { App } from './app'; @@ -35,7 +35,7 @@ export async function mountApp( coreStart.chrome.docTitle.change( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); - + const locationState = params.history.location.state as VisualizeFieldContext; const stateTransfer = embeddable?.getStateTransfer(params.history); const { originatingApp } = stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; @@ -73,7 +73,6 @@ export async function mountApp( const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { trackUiEvent('loaded'); - return ( ); }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index e628ea0675a8d0..02811cc5bb275a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -184,8 +184,8 @@ describe('editor_frame', () => { /> ); }); - expect(mockDatasource.initialize).toHaveBeenCalledWith(datasource1State, []); - expect(mockDatasource2.initialize).toHaveBeenCalledWith(datasource2State, []); + expect(mockDatasource.initialize).toHaveBeenCalledWith(datasource1State, [], undefined); + expect(mockDatasource2.initialize).toHaveBeenCalledWith(datasource2State, [], undefined); expect(mockDatasource3.initialize).not.toHaveBeenCalled(); }); @@ -972,6 +972,32 @@ describe('editor_frame', () => { }); describe('suggestions', () => { + it('should fetch suggestions of currently active datasource when initializes from visualization trigger', async () => { + await act(async () => { + mount( + + ); + }); + + expect(mockDatasource.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalled(); + }); + it('should fetch suggestions of currently active datasource', async () => { await act(async () => { mount( @@ -1208,6 +1234,7 @@ describe('editor_frame', () => { ...mockDatasource, getDatasourceSuggestionsForField: () => [generateSuggestion()], getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], + getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()], }, }} initialDatasourceId="testDatasource" @@ -1274,6 +1301,7 @@ describe('editor_frame', () => { ...mockDatasource, getDatasourceSuggestionsForField: () => [generateSuggestion()], getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], + getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()], renderDataPanel: (_element, { dragDropContext: { setDragging, dragging } }) => { if (dragging !== 'draggedField') { setDragging('draggedField'); @@ -1370,6 +1398,7 @@ describe('editor_frame', () => { ...mockDatasource, getDatasourceSuggestionsForField: () => [generateSuggestion()], getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()], + getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()], renderDataPanel: (_element, { dragDropContext: { setDragging, dragging } }) => { if (dragging !== 'draggedField') { setDragging('draggedField'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 72ad8e074226cc..e1ce22136d9bad 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -19,6 +19,7 @@ import { RootDragDropProvider } from '../../drag_drop'; import { getSavedObjectFormat } from './save'; import { generateId } from '../../id_generator'; import { Filter, Query, SavedQuery } from '../../../../../../src/plugins/data/public'; +import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; import { EditorFrameStartPlugins } from '../service'; import { initializeDatasources, createDatasourceLayers } from './state_helpers'; @@ -45,6 +46,7 @@ export interface EditorFrameProps { isSaveable: boolean; }) => void; showNoDataPopover: () => void; + visualizeTriggerFieldContext?: VisualizeFieldContext; } export function EditorFrame(props: EditorFrameProps) { @@ -63,7 +65,12 @@ export function EditorFrame(props: EditorFrameProps) { // prevents executing dispatch on unmounted component let isUnmounted = false; if (!allLoaded) { - initializeDatasources(props.datasourceMap, state.datasourceStates, props.doc?.references) + initializeDatasources( + props.datasourceMap, + state.datasourceStates, + props.doc?.references, + props.visualizeTriggerFieldContext + ) .then((result) => { if (!isUnmounted) { Object.entries(result).forEach(([datasourceId, { state: datasourceState }]) => { @@ -84,7 +91,6 @@ export function EditorFrame(props: EditorFrameProps) { // eslint-disable-next-line react-hooks/exhaustive-deps [allLoaded, onError] ); - const datasourceLayers = createDatasourceLayers(props.datasourceMap, state.datasourceStates); const framePublicAPI: FramePublicAPI = { @@ -275,6 +281,7 @@ export function EditorFrame(props: EditorFrameProps) { ExpressionRenderer={props.ExpressionRenderer} core={props.core} plugins={props.plugins} + visualizeTriggerFieldContext={props.visualizeTriggerFieldContext} /> ) } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 6deb9ffd37a06f..78d695880a2167 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -9,18 +9,24 @@ import { Ast } from '@kbn/interpreter/common'; import { Datasource, DatasourcePublicAPI, Visualization } from '../../types'; import { buildExpression } from './expression_helpers'; import { Document } from '../../persistence/saved_object_store'; +import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; export async function initializeDatasources( datasourceMap: Record, datasourceStates: Record, - references?: SavedObjectReference[] + references?: SavedObjectReference[], + visualizeTriggerFieldContext?: VisualizeFieldContext ) { const states: Record = {}; await Promise.all( Object.entries(datasourceMap).map(([datasourceId, datasource]) => { if (datasourceStates[datasourceId]) { return datasource - .initialize(datasourceStates[datasourceId].state || undefined, references) + .initialize( + datasourceStates[datasourceId].state || undefined, + references, + visualizeTriggerFieldContext + ) .then((datasourceState) => { states[datasourceId] = { isLoading: false, state: datasourceState }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts index 63b8b1f0482968..c5c66c1c820e86 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts @@ -173,6 +173,75 @@ describe('suggestion helpers', () => { expect(multiDatasourceMap.mock3.getDatasourceSuggestionsForField).not.toHaveBeenCalled(); }); + it('should call getDatasourceSuggestionsForVisualizeField when a visualizeTriggerField is passed', () => { + datasourceMap.mock.getDatasourceSuggestionsForVisualizeField.mockReturnValue([ + generateSuggestion(), + ]); + getSuggestions({ + visualizationMap: { + vis1: createMockVisualization(), + }, + activeVisualizationId: 'vis1', + visualizationState: {}, + datasourceMap, + datasourceStates, + visualizeTriggerFieldContext: { + indexPatternId: '1', + fieldName: 'test', + }, + }); + expect(datasourceMap.mock.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( + datasourceStates.mock.state, + '1', + 'test' + ); + }); + + it('should call getDatasourceSuggestionsForVisualizeField from all datasources with a state', () => { + const multiDatasourceStates = { + mock: { + isLoading: false, + state: {}, + }, + mock2: { + isLoading: false, + state: {}, + }, + }; + const multiDatasourceMap = { + mock: createMockDatasource('a'), + mock2: createMockDatasource('a'), + mock3: createMockDatasource('a'), + }; + const visualizeTriggerField = { + indexPatternId: '1', + fieldName: 'test', + }; + getSuggestions({ + visualizationMap: { + vis1: createMockVisualization(), + }, + activeVisualizationId: 'vis1', + visualizationState: {}, + datasourceMap: multiDatasourceMap, + datasourceStates: multiDatasourceStates, + visualizeTriggerFieldContext: visualizeTriggerField, + }); + expect(multiDatasourceMap.mock.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( + multiDatasourceStates.mock.state, + '1', + 'test' + ); + expect(multiDatasourceMap.mock2.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledWith( + multiDatasourceStates.mock2.state, + '1', + 'test' + ); + expect( + multiDatasourceMap.mock3.getDatasourceSuggestionsForVisualizeField + ).not.toHaveBeenCalled(); + }); + it('should rank the visualizations by score', () => { const mockVisualization1 = createMockVisualization(); const mockVisualization2 = createMockVisualization(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index 2bb1baf9d54f25..e0c125ed4e7240 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -7,6 +7,7 @@ import _ from 'lodash'; import { Ast } from '@kbn/interpreter/common'; import { IconType } from '@elastic/eui/src/components/icon/icon'; +import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; import { Visualization, Datasource, @@ -47,6 +48,7 @@ export function getSuggestions({ subVisualizationId, visualizationState, field, + visualizeTriggerFieldContext, }: { datasourceMap: Record; datasourceStates: Record< @@ -61,6 +63,7 @@ export function getSuggestions({ subVisualizationId?: string; visualizationState: unknown; field?: unknown; + visualizeTriggerFieldContext?: VisualizeFieldContext; }): Suggestion[] { const datasources = Object.entries(datasourceMap).filter( ([datasourceId]) => datasourceStates[datasourceId] && !datasourceStates[datasourceId].isLoading @@ -70,10 +73,16 @@ export function getSuggestions({ const datasourceTableSuggestions = _.flatten( datasources.map(([datasourceId, datasource]) => { const datasourceState = datasourceStates[datasourceId].state; - return (field + const dataSourceSuggestions = visualizeTriggerFieldContext + ? datasource.getDatasourceSuggestionsForVisualizeField( + datasourceState, + visualizeTriggerFieldContext.indexPatternId, + visualizeTriggerFieldContext.fieldName + ) + : field ? datasource.getDatasourceSuggestionsForField(datasourceState, field) - : datasource.getDatasourceSuggestionsFromCurrentState(datasourceState) - ).map((suggestion) => ({ ...suggestion, datasourceId })); + : datasource.getDatasourceSuggestionsFromCurrentState(datasourceState); + return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId })); }) ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 06cd858eda210b..04373a1498a7b0 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -35,7 +35,10 @@ import { getSuggestions, switchToSuggestion } from '../suggestion_helpers'; import { buildExpression } from '../expression_helpers'; import { debouncedComponent } from '../../../debounced_component'; import { trackUiEvent } from '../../../lens_ui_telemetry'; -import { UiActionsStart } from '../../../../../../../src/plugins/ui_actions/public'; +import { + UiActionsStart, + VisualizeFieldContext, +} from '../../../../../../../src/plugins/ui_actions/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; import { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; @@ -59,6 +62,7 @@ export interface WorkspacePanelProps { core: CoreStart | CoreSetup; plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart }; title?: string; + visualizeTriggerFieldContext?: VisualizeFieldContext; } export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel); @@ -77,6 +81,7 @@ export function InnerWorkspacePanel({ plugins, ExpressionRenderer: ExpressionRendererComponent, title, + visualizeTriggerFieldContext, }: WorkspacePanelProps) { const IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); const emptyStateGraphicURL = IS_DARK_THEME @@ -116,6 +121,7 @@ export function InnerWorkspacePanel({ const [localState, setLocalState] = useState({ expressionBuildError: undefined as string | undefined, expandError: false, + visualizeTriggerSuggestionsLoaded: false, }); const activeVisualization = activeVisualizationId @@ -200,6 +206,31 @@ export function InnerWorkspacePanel({ } }, [expression, localState.expressionBuildError]); + useEffect(() => { + if ( + !activeDatasourceId || + !visualizeTriggerFieldContext || + localState.visualizeTriggerSuggestionsLoaded + ) { + return; + } + + const suggestions = getSuggestions({ + datasourceMap: { [activeDatasourceId]: datasourceMap[activeDatasourceId] }, + datasourceStates, + visualizationMap, + activeVisualizationId, + visualizationState, + visualizeTriggerFieldContext, + }); + + if (suggestions.length) { + switchToSuggestion(dispatch, suggestions[0], 'SWITCH_VISUALIZATION'); + setLocalState((s) => ({ ...s, visualizeTriggerSuggestionsLoaded: true })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visualizeTriggerFieldContext, visualizationState]); + function onDrop() { if (suggestionForDraggedField) { trackUiEvent('drop_onto_workspace'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index 86b137851d9bdd..93898ef1d43a87 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -69,6 +69,7 @@ export function createMockDatasource(id: string): DatasourceMock { id: 'mockindexpattern', clearLayer: jest.fn((state, _layerId) => state), getDatasourceSuggestionsForField: jest.fn((_state, _item) => []), + getDatasourceSuggestionsForVisualizeField: jest.fn((_state, _indexpatternId, _fieldName) => []), getDatasourceSuggestionsFromCurrentState: jest.fn((_state) => []), getPersistableState: jest.fn((x) => ({ state: x, savedObjectReferences: [] })), getPublicAPI: jest.fn().mockReturnValue(publicAPIMock), diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index 7b1d091c1c8fe9..cf0ed978dd1a4d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -52,6 +52,10 @@ describe('editor_frame service', () => { query: { query: '', language: 'lucene' }, filters: [], showNoDataPopover: jest.fn(), + visualizeTriggerFieldContext: { + indexPatternId: '1', + fieldName: 'test', + }, }); instance.unmount(); })() diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 5fc347179a032b..c4bdbb56f70fbc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -122,7 +122,17 @@ export class EditorFrameService { return { mount: ( element, - { doc, onError, dateRange, query, filters, savedQuery, onChange, showNoDataPopover } + { + doc, + onError, + dateRange, + query, + filters, + savedQuery, + onChange, + showNoDataPopover, + visualizeTriggerFieldContext, + } ) => { domElement = element; const firstDatasourceId = Object.keys(resolvedDatasources)[0]; @@ -149,6 +159,7 @@ export class EditorFrameService { savedQuery={savedQuery} onChange={onChange} showNoDataPopover={showNoDataPopover} + visualizeTriggerFieldContext={visualizeTriggerFieldContext} /> , domElement diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 3b3750cf7c560c..9e2aa78c6f132e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -36,6 +36,7 @@ import { IndexPatternDataPanel } from './datapanel'; import { getDatasourceSuggestionsForField, getDatasourceSuggestionsFromCurrentState, + getDatasourceSuggestionsForVisualizeField, } from './indexpattern_suggestions'; import { isDraggedField, normalizeOperationDataType } from './utils'; @@ -49,6 +50,7 @@ import { } from './types'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; import { deleteColumn } from './state_helpers'; import { Datasource, StateSetter } from '../index'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; @@ -132,7 +134,8 @@ export function getIndexPatternDatasource({ async initialize( persistedState?: IndexPatternPersistedState, - references?: SavedObjectReference[] + references?: SavedObjectReference[], + visualizeTriggerFieldContext?: VisualizeFieldContext ) { return loadInitialState({ persistedState, @@ -141,6 +144,7 @@ export function getIndexPatternDatasource({ defaultIndexPatternId: core.uiSettings.get('defaultIndex'), storage, indexPatternsService, + visualizeTriggerFieldContext, }); }, @@ -333,6 +337,7 @@ export function getIndexPatternDatasource({ : []; }, getDatasourceSuggestionsFromCurrentState, + getDatasourceSuggestionsForVisualizeField, }; return indexPatternDatasource; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index f3aa9c4f51c82f..a81fdd9e1ef48a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -117,6 +117,32 @@ export function getDatasourceSuggestionsForField( } } +export function getDatasourceSuggestionsForVisualizeField( + state: IndexPatternPrivateState, + indexPatternId: string, + fieldName: string +): IndexPatternSugestion[] { + const layers = Object.keys(state.layers); + const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); + const suggestions: IndexPatternSugestion[] = []; + if (layerIds.length === 0) return []; + const indexPattern = state.indexPatterns[indexPatternId]; + const field = indexPattern.fields.find((fld) => fld.name === fieldName); + if (!field) return []; + const mostEmptyLayerId = _.minBy( + layerIds, + (layerId) => state.layers[layerId].columnOrder.length + ) as string; + if (state.layers[mostEmptyLayerId].columnOrder.length === 0) { + suggestions.push( + ...getEmptyLayerSuggestionsForField(state, mostEmptyLayerId, indexPatternId, field) + ); + } else { + suggestions.push(...getExistingLayerSuggestionsForField(state, mostEmptyLayerId, field)); + } + return suggestions; +} + function getBucketOperation(field: IndexPatternField) { // We allow numeric bucket types in some cases, but it's generally not the right suggestion, // so we eliminate it here. @@ -471,7 +497,6 @@ export function getDatasourceSuggestionsFromCurrentState( suggestions.push(createChangedNestingSuggestion(state, layerId)); } } - return suggestions; }) ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index 19213d4afc9bcb..3cb55552784139 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -391,6 +391,34 @@ describe('loader', () => { }); }); + it('should use the indexPatternId of the visualize trigger field, if provided', async () => { + const storage = createMockStorage(); + const state = await loadInitialState({ + savedObjectsClient: mockClient(), + indexPatternsService: mockIndexPatternsService(), + storage, + visualizeTriggerFieldContext: { + indexPatternId: '1', + fieldName: '', + }, + }); + + expect(state).toMatchObject({ + currentIndexPatternId: '1', + indexPatternRefs: [ + { id: '1', title: sampleIndexPatterns['1'].title }, + { id: '2', title: sampleIndexPatterns['2'].title }, + ], + indexPatterns: { + '1': sampleIndexPatterns['1'], + }, + layers: {}, + }); + expect(storage.set).toHaveBeenCalledWith('lens-settings', { + indexPatternId: '1', + }); + }); + it('should initialize from saved state', async () => { const savedState: IndexPatternPersistedState = { layers: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 0ab658b9613363..735177a6d59610 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -23,6 +23,7 @@ import { IndexPatternsContract, indexPatterns as indexPatternsUtils, } from '../../../../../src/plugins/data/public'; +import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; import { documentField } from './document_field'; import { readFromStorage, writeToStorage } from '../settings_storage'; @@ -178,6 +179,7 @@ export async function loadInitialState({ defaultIndexPatternId, storage, indexPatternsService, + visualizeTriggerFieldContext, }: { persistedState?: IndexPatternPersistedState; references?: SavedObjectReference[]; @@ -185,6 +187,7 @@ export async function loadInitialState({ defaultIndexPatternId?: string; storage: IStorageWrapper; indexPatternsService: IndexPatternsService; + visualizeTriggerFieldContext?: VisualizeFieldContext; }): Promise { const indexPatternRefs = await loadIndexPatternRefs(savedObjectsClient); const lastUsedIndexPatternId = getLastUsedIndexPatternId(storage, indexPatternRefs); @@ -200,13 +203,15 @@ export async function loadInitialState({ : [lastUsedIndexPatternId || defaultIndexPatternId || indexPatternRefs[0].id] ); - const currentIndexPatternId = requiredPatterns[0]; + const currentIndexPatternId = visualizeTriggerFieldContext?.indexPatternId ?? requiredPatterns[0]; setLastUsedIndexPatternId(storage, currentIndexPatternId); const indexPatterns = await loadIndexPatterns({ indexPatternsService, cache: {}, - patterns: requiredPatterns, + patterns: visualizeTriggerFieldContext + ? [visualizeTriggerFieldContext.indexPatternId] + : requiredPatterns, }); if (state) { return { diff --git a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts index f666d3cd92ca91..3590aac4db1410 100644 --- a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts +++ b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts @@ -16,16 +16,13 @@ export const visualizeFieldAction = (application: ApplicationStart) => type: ACTION_VISUALIZE_LENS_FIELD, id: ACTION_VISUALIZE_LENS_FIELD, getDisplayName: () => - i18n.translate('visualize.discover.visualizeFieldLabel', { + i18n.translate('xpack.lens.discover.visualizeFieldLegend', { defaultMessage: 'Visualize field', }), isCompatible: async () => !!application.capabilities.visualize.show, execute: async (context: VisualizeFieldContext) => { application.navigateToApp('lens', { - state: { - fieldName: context.fieldName, - indexPatternId: context.indexPatternId, - }, + state: context, }); }, }); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index edb787d9ec1a12..291f18cd61abea 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -22,6 +22,7 @@ import { SELECT_RANGE_TRIGGER, TriggerContext, VALUE_CLICK_TRIGGER, + VisualizeFieldContext, } from '../../../../src/plugins/ui_actions/public'; export type ErrorCallback = (e: { message: string }) => void; @@ -40,6 +41,7 @@ export interface EditorFrameProps { query: Query; filters: Filter[]; savedQuery?: SavedQuery; + visualizeTriggerFieldContext?: VisualizeFieldContext; // Frame loader (app or embeddable) is expected to call this when it loads and updates // This should be replaced with a top-down state @@ -141,7 +143,11 @@ export interface Datasource { // For initializing, either from an empty state or from persisted state // Because this will be called at runtime, state might have a type of `any` and // datasources should validate their arguments - initialize: (state?: P, savedObjectReferences?: SavedObjectReference[]) => Promise; + initialize: ( + state?: P, + savedObjectReferences?: SavedObjectReference[], + visualizeTriggerFieldContext?: VisualizeFieldContext + ) => Promise; // Given the current state, which parts should be saved? getPersistableState: (state: T) => { state: P; savedObjectReferences: SavedObjectReference[] }; @@ -162,6 +168,11 @@ export interface Datasource { toExpression: (state: T, layerId: string) => Ast | string | null; getDatasourceSuggestionsForField: (state: T, field: unknown) => Array>; + getDatasourceSuggestionsForVisualizeField: ( + state: T, + indexPatternId: string, + fieldName: string + ) => Array>; getDatasourceSuggestionsFromCurrentState: (state: T) => Array>; getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI; From c018989ffb70bce6107c4d86dc6f3c9c9ac1d02a Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Sep 2020 10:15:58 +0300 Subject: [PATCH 04/15] Add unit tests --- .../workspace_panel/workspace_panel.test.tsx | 137 +++++++ .../indexpattern_suggestions.test.tsx | 375 ++++++++++++++++++ .../indexpattern_suggestions.ts | 22 +- 3 files changed, 521 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 47e3b41df3b21b..5c39e0c1e2f647 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -800,4 +800,141 @@ describe('workspace_panel', () => { }); }); }); + + describe('suggestions from navigating in workspace panel from Discover', () => { + let mockDispatch: jest.Mock; + let frame: jest.Mocked; + + const visualizeTriggerFieldContext = { + indexPatternId: '1', + fieldName: 'timestamp', + }; + + beforeEach(() => { + frame = createMockFramePublicAPI(); + mockDispatch = jest.fn(); + }); + + function initComponent() { + instance = mount( + + ); + } + + it('should immediately transition if exactly one suggestion is returned', () => { + const expectedTable: TableSuggestion = { + isMultiRow: true, + layerId: '1', + columns: [], + changeType: 'unchanged', + }; + mockDatasource.getDatasourceSuggestionsForVisualizeField.mockReturnValueOnce([ + { + state: {}, + table: expectedTable, + keptLayerIds: [], + }, + ]); + mockVisualization.getSuggestions.mockReturnValueOnce([ + { + score: 0.5, + title: 'my title', + state: {}, + previewIcon: 'empty', + }, + ]); + initComponent(); + + expect(mockDatasource.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledTimes(1); + expect(mockVisualization.getSuggestions).toHaveBeenCalledWith( + expect.objectContaining({ + table: expectedTable, + }) + ); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'SWITCH_VISUALIZATION', + newVisualizationId: 'vis', + initialState: {}, + datasourceState: {}, + datasourceId: 'mock', + }); + }); + + it('should immediately transition to the first suggestion if there are multiple', () => { + mockDatasource.getDatasourceSuggestionsForVisualizeField.mockReturnValueOnce([ + { + state: {}, + table: { + isMultiRow: true, + columns: [], + layerId: '1', + changeType: 'unchanged', + }, + keptLayerIds: [], + }, + { + state: {}, + table: { + isMultiRow: true, + columns: [], + layerId: '1', + changeType: 'unchanged', + }, + keptLayerIds: [], + }, + ]); + mockVisualization.getSuggestions.mockReturnValueOnce([ + { + score: 0.5, + title: 'second suggestion', + state: {}, + previewIcon: 'empty', + }, + ]); + mockVisualization.getSuggestions.mockReturnValueOnce([ + { + score: 0.8, + title: 'first suggestion', + state: { + isFirst: true, + }, + previewIcon: 'empty', + }, + ]); + + initComponent(); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'SWITCH_VISUALIZATION', + newVisualizationId: 'vis', + initialState: { + isFirst: true, + }, + datasourceState: {}, + datasourceId: 'mock', + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 663d7c18bb370d..f26f07c65affc6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -10,6 +10,7 @@ import { IndexPatternPrivateState } from './types'; import { getDatasourceSuggestionsForField, getDatasourceSuggestionsFromCurrentState, + getDatasourceSuggestionsForVisualizeField, } from './indexpattern_suggestions'; jest.mock('./loader'); @@ -1077,6 +1078,380 @@ describe('IndexPattern Data Source suggestions', () => { }); }); }); + describe('#getDatasourceSuggestionsForVisualizeField', () => { + describe('with no layer', () => { + function stateWithoutLayer() { + return { + ...testInitialState(), + layers: {}, + }; + } + + it('should return an empty array', () => { + const suggestions = getDatasourceSuggestionsForVisualizeField( + stateWithoutLayer(), + '1', + 'source' + ); + + expect(suggestions).toEqual([]); + }); + }); + describe('with a previous empty layer', () => { + function stateWithEmptyLayer() { + const state = testInitialState(); + return { + ...state, + layers: { + previousLayer: { + indexPatternId: '1', + columns: {}, + columnOrder: [], + }, + }, + }; + } + + it('should return an empty array if the field does not exist', () => { + const suggestions = getDatasourceSuggestionsForVisualizeField( + stateWithEmptyLayer(), + '1', + 'field_not_exist' + ); + + expect(suggestions).toEqual([]); + }); + + it('should apply a bucketed aggregation for a string field', () => { + const suggestions = getDatasourceSuggestionsForVisualizeField( + stateWithEmptyLayer(), + '1', + 'source' + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: expect.objectContaining({ + columnOrder: ['id1', 'id2'], + columns: { + id1: expect.objectContaining({ + operationType: 'terms', + sourceField: 'source', + params: expect.objectContaining({ size: 5 }), + }), + id2: expect.objectContaining({ + operationType: 'count', + }), + }, + }), + }, + }), + table: { + changeType: 'initial', + label: undefined, + isMultiRow: true, + columns: [ + expect.objectContaining({ + columnId: 'id1', + }), + expect.objectContaining({ + columnId: 'id2', + }), + ], + layerId: 'previousLayer', + }, + }) + ); + }); + + it('should apply a bucketed aggregation for a date field', () => { + const suggestions = getDatasourceSuggestionsForVisualizeField( + stateWithEmptyLayer(), + '1', + 'timestamp' + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: expect.objectContaining({ + columnOrder: ['id1', 'id2'], + columns: { + id1: expect.objectContaining({ + operationType: 'date_histogram', + sourceField: 'timestamp', + }), + id2: expect.objectContaining({ + operationType: 'count', + }), + }, + }), + }, + }), + table: { + changeType: 'initial', + label: undefined, + isMultiRow: true, + columns: [ + expect.objectContaining({ + columnId: 'id1', + }), + expect.objectContaining({ + columnId: 'id2', + }), + ], + layerId: 'previousLayer', + }, + }) + ); + }); + }); + + describe('suggesting extensions to non-empty tables', () => { + function stateWithNonEmptyTables(): IndexPatternPrivateState { + const state = testInitialState(); + + return { + ...state, + currentIndexPatternId: '1', + layers: { + previousLayer: { + indexPatternId: '2', + columns: {}, + columnOrder: [], + }, + currentLayer: { + indexPatternId: '1', + columns: { + cola: { + dataType: 'string', + isBucketed: true, + sourceField: 'source', + label: 'values of source', + operationType: 'terms', + params: { + orderBy: { type: 'column', columnId: 'colb' }, + orderDirection: 'asc', + size: 5, + }, + }, + colb: { + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + label: 'Avg of bytes', + operationType: 'avg', + }, + }, + columnOrder: ['cola', 'colb'], + }, + }, + }; + } + + it('replaces an existing date histogram column on date field', () => { + const initialState = stateWithNonEmptyTables(); + const suggestions = getDatasourceSuggestionsForVisualizeField( + { + ...initialState, + layers: { + previousLayer: initialState.layers.previousLayer, + currentLayer: { + ...initialState.layers.currentLayer, + columns: { + cola: { + dataType: 'date', + isBucketed: true, + sourceField: 'timestamp', + label: 'date histogram of timestamp', + operationType: 'date_histogram', + params: { + interval: 'w', + }, + }, + colb: { + dataType: 'number', + isBucketed: false, + sourceField: 'bytes', + label: 'Avg of bytes', + operationType: 'avg', + }, + }, + }, + }, + }, + '1', + 'start_date' + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: initialState.layers.previousLayer, + currentLayer: expect.objectContaining({ + columnOrder: ['id1', 'colb'], + columns: { + id1: expect.objectContaining({ + operationType: 'date_histogram', + sourceField: 'start_date', + }), + colb: initialState.layers.currentLayer.columns.colb, + }, + }), + }, + }), + }) + ); + }); + + it('does not use the same field for bucketing multiple times', () => { + const suggestions = getDatasourceSuggestionsForVisualizeField( + stateWithNonEmptyTables(), + '1', + 'source' + ); + + expect(suggestions).toHaveLength(1); + // Check that the suggestion is a single metric + expect(suggestions[0].table.columns).toHaveLength(1); + expect(suggestions[0].table.columns[0].operation.isBucketed).toBeFalsy(); + }); + + it('replaces a metric column on a number field if only one other metric is already set', () => { + const initialState = stateWithNonEmptyTables(); + const suggestions = getDatasourceSuggestionsForVisualizeField(initialState, '1', 'memory'); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: expect.objectContaining({ + currentLayer: expect.objectContaining({ + columnOrder: ['cola', 'colb'], + columns: { + cola: initialState.layers.currentLayer.columns.cola, + colb: expect.objectContaining({ + operationType: 'avg', + sourceField: 'memory', + }), + }, + }), + }), + }), + }) + ); + }); + + it('adds a metric column on a number field if no other metrics set', () => { + const initialState = stateWithNonEmptyTables(); + const modifiedState: IndexPatternPrivateState = { + ...initialState, + layers: { + ...initialState.layers, + currentLayer: { + ...initialState.layers.currentLayer, + columns: { + cola: initialState.layers.currentLayer.columns.cola, + }, + columnOrder: ['cola'], + }, + }, + }; + const suggestions = getDatasourceSuggestionsForVisualizeField(modifiedState, '1', 'memory'); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: modifiedState.layers.previousLayer, + currentLayer: expect.objectContaining({ + columnOrder: ['cola', 'id1'], + columns: { + ...modifiedState.layers.currentLayer.columns, + id1: expect.objectContaining({ + operationType: 'avg', + sourceField: 'memory', + }), + }, + }), + }, + }), + }) + ); + }); + }); + + describe('finding the layer that is using the current index pattern', () => { + function stateWithCurrentIndexPattern(): IndexPatternPrivateState { + const state = testInitialState(); + + return { + ...state, + currentIndexPatternId: '1', + layers: { + previousLayer: { + indexPatternId: '1', + columns: {}, + columnOrder: [], + }, + currentLayer: { + indexPatternId: '2', + columns: {}, + columnOrder: [], + }, + }, + }; + } + + it('suggests on the layer that matches by indexPatternId', () => { + const initialState = stateWithCurrentIndexPattern(); + const suggestions = getDatasourceSuggestionsForVisualizeField( + initialState, + '2', + 'timestamp' + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: initialState.layers.previousLayer, + currentLayer: expect.objectContaining({ + columnOrder: ['id1', 'id2'], + columns: { + id1: expect.objectContaining({ + operationType: 'date_histogram', + sourceField: 'timestamp', + }), + id2: expect.objectContaining({ + operationType: 'count', + }), + }, + }), + }, + }), + table: { + changeType: 'initial', + label: undefined, + isMultiRow: true, + columns: [ + expect.objectContaining({ + columnId: 'id1', + }), + expect.objectContaining({ + columnId: 'id2', + }), + ], + layerId: 'currentLayer', + }, + }) + ); + }); + }); + }); describe('#getDatasourceSuggestionsFromCurrentState', () => { it('returns no suggestions if there are no columns', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index a81fdd9e1ef48a..757cc5efa81cc0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -117,6 +117,7 @@ export function getDatasourceSuggestionsForField( } } +// Called when the user navigates from Discover to Lens (Visualize button) export function getDatasourceSuggestionsForVisualizeField( state: IndexPatternPrivateState, indexPatternId: string, @@ -124,23 +125,18 @@ export function getDatasourceSuggestionsForVisualizeField( ): IndexPatternSugestion[] { const layers = Object.keys(state.layers); const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); - const suggestions: IndexPatternSugestion[] = []; - if (layerIds.length === 0) return []; + // Identify the field by the indexPatternId and the fieldName const indexPattern = state.indexPatterns[indexPatternId]; const field = indexPattern.fields.find((fld) => fld.name === fieldName); - if (!field) return []; - const mostEmptyLayerId = _.minBy( - layerIds, - (layerId) => state.layers[layerId].columnOrder.length - ) as string; - if (state.layers[mostEmptyLayerId].columnOrder.length === 0) { - suggestions.push( - ...getEmptyLayerSuggestionsForField(state, mostEmptyLayerId, indexPatternId, field) - ); + + if (layerIds.length === 0 || !field) return []; + + const layerId = layerIds[0]; + if (state.layers[layerId].columnOrder.length === 0) { + return getEmptyLayerSuggestionsForField(state, layerId, indexPatternId, field); } else { - suggestions.push(...getExistingLayerSuggestionsForField(state, mostEmptyLayerId, field)); + return getExistingLayerSuggestionsForField(state, layerId, field); } - return suggestions; } function getBucketOperation(field: IndexPatternField) { From 80e56ae5aa888dcd9fce2371e493426e275697de Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Sep 2020 10:52:38 +0300 Subject: [PATCH 05/15] fix embed lens to empty dashboard functional tests --- x-pack/plugins/lens/public/app_plugin/app.test.tsx | 4 ++-- x-pack/plugins/lens/public/app_plugin/app.tsx | 6 +++--- x-pack/plugins/lens/public/app_plugin/mounter.tsx | 8 ++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 9477f2f985e861..baaf5af2461caf 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -132,7 +132,7 @@ describe('Lens App', () => { core: typeof core; storage: Storage; docId?: string; - locationState?: VisualizeFieldContext; + visualizeTriggerFieldContext?: VisualizeFieldContext; docStorage: SavedObjectStore; redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; originatingApp: string | undefined; @@ -187,7 +187,7 @@ describe('Lens App', () => { core: typeof core; storage: Storage; docId?: string; - locationState?: VisualizeFieldContext; + visualizeTriggerFieldContext?: VisualizeFieldContext; docStorage: SavedObjectStore; redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; originatingApp: string | undefined; diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 2ef2f6f172b9c5..b625b8518bae9b 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -76,7 +76,7 @@ export function App({ setHeaderActionMenu, history, getAppNameFromId, - locationState, + visualizeTriggerFieldContext, }: { editorFrame: EditorFrameInstance; data: DataPublicPluginStart; @@ -91,7 +91,7 @@ export function App({ setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; history: History; getAppNameFromId?: (appId: string) => string | undefined; - locationState?: VisualizeFieldContext; + visualizeTriggerFieldContext?: VisualizeFieldContext; }) { const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); @@ -544,7 +544,7 @@ export function App({ filters: state.filters, savedQuery: state.savedQuery, doc: state.persistedDoc, - visualizeTriggerFieldContext: locationState, + visualizeTriggerFieldContext, onError, showNoDataPopover, onChange: ({ filterableIndexPatterns, doc, isSaveable }) => { diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index eafe38d210e261..829e7e278f048c 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -35,7 +35,7 @@ export async function mountApp( coreStart.chrome.docTitle.change( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); - const locationState = params.history.location.state as VisualizeFieldContext; + const historyLocationState = params.history.location.state as VisualizeFieldContext; const stateTransfer = embeddable?.getStateTransfer(params.history); const { originatingApp } = stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; @@ -90,7 +90,11 @@ export async function mountApp( onAppLeave={params.onAppLeave} setHeaderActionMenu={params.setHeaderActionMenu} history={routeProps.history} - locationState={locationState} + visualizeTriggerFieldContext={ + historyLocationState && 'indexPatternId' in historyLocationState + ? historyLocationState + : undefined + } /> ); }; From 03b5cb9ebbea09237c9f91a1e1265feb1f1957ae Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Sep 2020 11:40:05 +0300 Subject: [PATCH 06/15] fix suggestions on save --- .../indexpattern_suggestions.test.tsx | 174 ------------------ .../indexpattern_suggestions.ts | 7 +- 2 files changed, 1 insertion(+), 180 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index f26f07c65affc6..629d5bb9f30bbc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -1210,180 +1210,6 @@ describe('IndexPattern Data Source suggestions', () => { }); }); - describe('suggesting extensions to non-empty tables', () => { - function stateWithNonEmptyTables(): IndexPatternPrivateState { - const state = testInitialState(); - - return { - ...state, - currentIndexPatternId: '1', - layers: { - previousLayer: { - indexPatternId: '2', - columns: {}, - columnOrder: [], - }, - currentLayer: { - indexPatternId: '1', - columns: { - cola: { - dataType: 'string', - isBucketed: true, - sourceField: 'source', - label: 'values of source', - operationType: 'terms', - params: { - orderBy: { type: 'column', columnId: 'colb' }, - orderDirection: 'asc', - size: 5, - }, - }, - colb: { - dataType: 'number', - isBucketed: false, - sourceField: 'bytes', - label: 'Avg of bytes', - operationType: 'avg', - }, - }, - columnOrder: ['cola', 'colb'], - }, - }, - }; - } - - it('replaces an existing date histogram column on date field', () => { - const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForVisualizeField( - { - ...initialState, - layers: { - previousLayer: initialState.layers.previousLayer, - currentLayer: { - ...initialState.layers.currentLayer, - columns: { - cola: { - dataType: 'date', - isBucketed: true, - sourceField: 'timestamp', - label: 'date histogram of timestamp', - operationType: 'date_histogram', - params: { - interval: 'w', - }, - }, - colb: { - dataType: 'number', - isBucketed: false, - sourceField: 'bytes', - label: 'Avg of bytes', - operationType: 'avg', - }, - }, - }, - }, - }, - '1', - 'start_date' - ); - - expect(suggestions).toContainEqual( - expect.objectContaining({ - state: expect.objectContaining({ - layers: { - previousLayer: initialState.layers.previousLayer, - currentLayer: expect.objectContaining({ - columnOrder: ['id1', 'colb'], - columns: { - id1: expect.objectContaining({ - operationType: 'date_histogram', - sourceField: 'start_date', - }), - colb: initialState.layers.currentLayer.columns.colb, - }, - }), - }, - }), - }) - ); - }); - - it('does not use the same field for bucketing multiple times', () => { - const suggestions = getDatasourceSuggestionsForVisualizeField( - stateWithNonEmptyTables(), - '1', - 'source' - ); - - expect(suggestions).toHaveLength(1); - // Check that the suggestion is a single metric - expect(suggestions[0].table.columns).toHaveLength(1); - expect(suggestions[0].table.columns[0].operation.isBucketed).toBeFalsy(); - }); - - it('replaces a metric column on a number field if only one other metric is already set', () => { - const initialState = stateWithNonEmptyTables(); - const suggestions = getDatasourceSuggestionsForVisualizeField(initialState, '1', 'memory'); - - expect(suggestions).toContainEqual( - expect.objectContaining({ - state: expect.objectContaining({ - layers: expect.objectContaining({ - currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'colb'], - columns: { - cola: initialState.layers.currentLayer.columns.cola, - colb: expect.objectContaining({ - operationType: 'avg', - sourceField: 'memory', - }), - }, - }), - }), - }), - }) - ); - }); - - it('adds a metric column on a number field if no other metrics set', () => { - const initialState = stateWithNonEmptyTables(); - const modifiedState: IndexPatternPrivateState = { - ...initialState, - layers: { - ...initialState.layers, - currentLayer: { - ...initialState.layers.currentLayer, - columns: { - cola: initialState.layers.currentLayer.columns.cola, - }, - columnOrder: ['cola'], - }, - }, - }; - const suggestions = getDatasourceSuggestionsForVisualizeField(modifiedState, '1', 'memory'); - - expect(suggestions).toContainEqual( - expect.objectContaining({ - state: expect.objectContaining({ - layers: { - previousLayer: modifiedState.layers.previousLayer, - currentLayer: expect.objectContaining({ - columnOrder: ['cola', 'id1'], - columns: { - ...modifiedState.layers.currentLayer.columns, - id1: expect.objectContaining({ - operationType: 'avg', - sourceField: 'memory', - }), - }, - }), - }, - }), - }) - ); - }); - }); - describe('finding the layer that is using the current index pattern', () => { function stateWithCurrentIndexPattern(): IndexPatternPrivateState { const state = testInitialState(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index 757cc5efa81cc0..e29f32fbd9c9aa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -130,13 +130,8 @@ export function getDatasourceSuggestionsForVisualizeField( const field = indexPattern.fields.find((fld) => fld.name === fieldName); if (layerIds.length === 0 || !field) return []; - const layerId = layerIds[0]; - if (state.layers[layerId].columnOrder.length === 0) { - return getEmptyLayerSuggestionsForField(state, layerId, indexPatternId, field); - } else { - return getExistingLayerSuggestionsForField(state, layerId, field); - } + return getEmptyLayerSuggestionsForField(state, layerId, indexPatternId, field); } function getBucketOperation(field: IndexPatternField) { From 601361b1ebcf1f43087cf07882baa2aac4eb7cdb Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Sep 2020 18:28:36 +0300 Subject: [PATCH 07/15] Fix bug on save button, query and filters should be transferred from discover --- x-pack/plugins/lens/public/app_plugin/app.tsx | 10 ++++++++-- .../editor_frame/workspace_panel/workspace_panel.tsx | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index b625b8518bae9b..b7a24adc8a6875 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -99,7 +99,9 @@ export function App({ isLoading: !!docId, isSaveModalVisible: false, indexPatternsForTopNav: [], - query: data.query.queryString.getDefaultQuery(), + query: visualizeTriggerFieldContext + ? data.query.queryString.getQuery() + : data.query.queryString.getDefaultQuery(), dateRange: { fromDate: currentRange.from, toDate: currentRange.to, @@ -134,7 +136,10 @@ export function App({ useEffect(() => { // Clear app-specific filters when navigating to Lens. Necessary because Lens - // can be loaded without a full page refresh + // can be loaded without a full page refresh. If the user navigates to Lens from Discover + // we keep the filters + if (visualizeTriggerFieldContext) return; + data.query.filterManager.setAppFilters([]); const filterSubscription = data.query.filterManager.getUpdates$().subscribe({ @@ -179,6 +184,7 @@ export function App({ core.uiSettings, data.query, history, + visualizeTriggerFieldContext, ]); const getLastKnownDocWithoutPinnedFilters = useCallback( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 04373a1498a7b0..dfef6b390091d5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -210,7 +210,8 @@ export function InnerWorkspacePanel({ if ( !activeDatasourceId || !visualizeTriggerFieldContext || - localState.visualizeTriggerSuggestionsLoaded + localState.visualizeTriggerSuggestionsLoaded || + title ) { return; } @@ -225,7 +226,9 @@ export function InnerWorkspacePanel({ }); if (suggestions.length) { - switchToSuggestion(dispatch, suggestions[0], 'SWITCH_VISUALIZATION'); + const selectedSuggestion = + suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; + switchToSuggestion(dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION'); setLocalState((s) => ({ ...s, visualizeTriggerSuggestionsLoaded: true })); } // eslint-disable-next-line react-hooks/exhaustive-deps From b50e628cf9b7a00d7e819eab403a74d2e6ecbe7b Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 24 Sep 2020 11:56:07 +0300 Subject: [PATCH 08/15] Add functional test for the navigation from Discover to Lens --- x-pack/test/functional/apps/discover/index.ts | 1 + .../apps/discover/visualize_field.ts | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 x-pack/test/functional/apps/discover/visualize_field.ts diff --git a/x-pack/test/functional/apps/discover/index.ts b/x-pack/test/functional/apps/discover/index.ts index 759225d80fa206..0cf98c2088a409 100644 --- a/x-pack/test/functional/apps/discover/index.ts +++ b/x-pack/test/functional/apps/discover/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./async_scripted_fields')); loadTestFile(require.resolve('./reporting')); loadTestFile(require.resolve('./error_handling')); + loadTestFile(require.resolve('./visualize_field')); }); } diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts new file mode 100644 index 00000000000000..b559054d82b659 --- /dev/null +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -0,0 +1,71 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const filterBar = getService('filterBar'); + const queryBar = getService('queryBar'); + const PageObjects = getPageObjects([ + 'common', + 'error', + 'discover', + 'timePicker', + 'security', + 'spaceSelector', + 'lens', + 'header', + ]); + + async function setDiscoverTimeRange() { + await PageObjects.timePicker.setDefaultAbsoluteRange(); + } + + describe('discover field visualize button', () => { + beforeEach(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('lens/basic'); + await PageObjects.common.navigateToApp('discover'); + await setDiscoverTimeRange(); + }); + + after(async () => { + await esArchiver.unload('lens/basic'); + }); + + it('shows "visualize" field button', async () => { + await PageObjects.discover.clickFieldListItem('bytes'); + await PageObjects.discover.expectFieldListItemVisualize('bytes'); + }); + + it('visualizes field to Lens and saves it', async () => { + await PageObjects.discover.findFieldByName('bytes'); + await PageObjects.discover.clickFieldListItemVisualize('bytes'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.save('VisualizeFieldVis'); + }); + + it('should preserve app filters in lens', async () => { + await filterBar.addFilter('bytes', 'is between', '3500', '4000'); + await PageObjects.discover.findFieldByName('geo.src'); + await PageObjects.discover.clickFieldListItemVisualize('geo.src'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + expect(await filterBar.hasFilter('bytes', '3,500 to 4,000')).to.be(true); + }); + + it('should preserve query in lens', async () => { + await queryBar.setQuery('machine.os : ios'); + await queryBar.submitQuery(); + await PageObjects.discover.findFieldByName('geo.dest'); + await PageObjects.discover.clickFieldListItemVisualize('geo.dest'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); + }); + }); +} From 1519f936092541bf779e72dc1e56d546855ac653 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Sep 2020 11:36:12 +0300 Subject: [PATCH 09/15] PR update after code review --- x-pack/plugins/lens/kibana.json | 2 +- .../lens/public/app_plugin/app.test.tsx | 2 +- x-pack/plugins/lens/public/app_plugin/app.tsx | 16 +++---- .../lens/public/app_plugin/mounter.tsx | 12 +++--- .../plugins/lens/public/app_plugin/types.ts | 12 +++++- .../editor_frame/editor_frame.test.tsx | 2 +- .../editor_frame/editor_frame.tsx | 6 +-- .../editor_frame/state_helpers.ts | 8 +--- .../editor_frame/suggestion_helpers.ts | 23 ++++++---- .../workspace_panel/workspace_panel.tsx | 17 ++------ .../editor_frame_service/service.test.tsx | 2 +- .../public/editor_frame_service/service.tsx | 4 +- .../indexpattern_datasource/indexpattern.tsx | 4 +- .../indexpattern_suggestions.test.tsx | 43 ------------------- .../indexpattern_datasource/loader.test.ts | 2 +- .../public/indexpattern_datasource/loader.ts | 10 ++--- .../visualize_field_actions.ts | 2 +- x-pack/plugins/lens/public/types.ts | 4 +- 18 files changed, 61 insertions(+), 110 deletions(-) diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 67b9482e281524..46a0b56a03ec57 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -14,7 +14,7 @@ "charts", "uiActions" ], - "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions", "globalSearch"], + "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "globalSearch"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"] diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 199845cbcce0d8..ee92c52a3cc768 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -280,6 +280,7 @@ describe('Lens App', () => { }, "doc": undefined, "filters": Array [], + "initialContext": undefined, "onChange": [Function], "onError": [Function], "query": Object { @@ -288,7 +289,6 @@ describe('Lens App', () => { }, "savedQuery": undefined, "showNoDataPopover": [Function], - "visualizeTriggerFieldContext": undefined, }, ], ] diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 6d7bbb9ca8607c..2c2050fd99243f 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -45,7 +45,7 @@ export function App({ incomingState, redirectToOrigin, setHeaderActionMenu, - visualizeTriggerFieldContext, + initialContext, }: LensAppProps) { const { data, @@ -66,9 +66,7 @@ export function App({ const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); return { - query: visualizeTriggerFieldContext - ? data.query.queryString.getQuery() - : data.query.queryString.getDefaultQuery(), + query: data.query.queryString.getQuery(), filters: data.query.filterManager.getFilters(), isLoading: Boolean(initialInput), indexPatternsForTopNav: [], @@ -145,9 +143,9 @@ export function App({ // Clear app-specific filters when navigating to Lens. Necessary because Lens // can be loaded without a full page refresh. If the user navigates to Lens from Discover // we keep the filters - if (visualizeTriggerFieldContext) return; - - data.query.filterManager.setAppFilters([]); + if (!initialContext) { + data.query.filterManager.setAppFilters([]); + } const filterSubscription = data.query.filterManager.getUpdates$().subscribe({ next: () => { @@ -191,7 +189,7 @@ export function App({ uiSettings, data.query, history, - visualizeTriggerFieldContext, + initialContext, ]); useEffect(() => { @@ -581,7 +579,7 @@ export function App({ doc: state.persistedDoc, onError, showNoDataPopover, - visualizeTriggerFieldContext, + initialContext, onChange: ({ filterableIndexPatterns, doc, isSaveable }) => { if (isSaveable !== state.isSaveable) { setState((s) => ({ ...s, isSaveable })); diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7819f3c545a3a2..90ba74decfd809 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -26,9 +26,9 @@ import { LensByReferenceInput, LensByValueInput, } from '../editor_frame_service/embeddable/embeddable'; -import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; +import { ACTION_VISUALIZE_LENS_FIELD } from '../../../../../src/plugins/ui_actions/public'; import { LensAttributeService } from '../lens_attribute_service'; -import { LensAppServices, RedirectToOriginProps } from './types'; +import { LensAppServices, RedirectToOriginProps, HistoryLocationState } from './types'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; export async function mountApp( @@ -47,7 +47,7 @@ export async function mountApp( const instance = await createEditorFrame(); const storage = new Storage(localStorage); const stateTransfer = embeddable?.getStateTransfer(params.history); - const historyLocationState = params.history.location.state as VisualizeFieldContext; + const historyLocationState = params.history.location.state as HistoryLocationState; const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(); const lensServices: LensAppServices = { @@ -134,9 +134,9 @@ export async function mountApp( onAppLeave={params.onAppLeave} setHeaderActionMenu={params.setHeaderActionMenu} history={routeProps.history} - visualizeTriggerFieldContext={ - historyLocationState && 'indexPatternId' in historyLocationState - ? historyLocationState + initialContext={ + historyLocationState && historyLocationState.type === ACTION_VISUALIZE_LENS_FIELD + ? historyLocationState.payload : undefined } /> diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index 7d4f6a933175ec..bd5a9b5a8ed0a0 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -28,7 +28,10 @@ import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigati import { LensAttributeService } from '../lens_attribute_service'; import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DashboardFeatureFlagConfig } from '../../../../../src/plugins/dashboard/public'; -import { VisualizeFieldContext } from '../../../../../src/plugins/ui_actions/public'; +import { + VisualizeFieldContext, + ACTION_VISUALIZE_LENS_FIELD, +} from '../../../../../src/plugins/ui_actions/public'; import { EmbeddableEditorState } from '../../../../../src/plugins/embeddable/public'; import { EditorFrameInstance } from '..'; @@ -76,7 +79,12 @@ export interface LensAppProps { // State passed in by the container which is used to determine the id of the Originating App. incomingState?: EmbeddableEditorState; - visualizeTriggerFieldContext?: VisualizeFieldContext; + initialContext?: VisualizeFieldContext; +} + +export interface HistoryLocationState { + type: typeof ACTION_VISUALIZE_LENS_FIELD; + payload: VisualizeFieldContext; } export interface LensAppServices { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 02811cc5bb275a..7328cdaf6fc9bb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -977,7 +977,7 @@ describe('editor_frame', () => { mount( void; showNoDataPopover: () => void; - visualizeTriggerFieldContext?: VisualizeFieldContext; + initialContext?: VisualizeFieldContext; } export function EditorFrame(props: EditorFrameProps) { @@ -69,7 +69,7 @@ export function EditorFrame(props: EditorFrameProps) { props.datasourceMap, state.datasourceStates, props.doc?.references, - props.visualizeTriggerFieldContext + props.initialContext ) .then((result) => { if (!isUnmounted) { @@ -281,7 +281,7 @@ export function EditorFrame(props: EditorFrameProps) { ExpressionRenderer={props.ExpressionRenderer} core={props.core} plugins={props.plugins} - visualizeTriggerFieldContext={props.visualizeTriggerFieldContext} + visualizeTriggerFieldContext={props.initialContext} /> ) } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 78d695880a2167..775e5d337f515e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -15,18 +15,14 @@ export async function initializeDatasources( datasourceMap: Record, datasourceStates: Record, references?: SavedObjectReference[], - visualizeTriggerFieldContext?: VisualizeFieldContext + initialContext?: VisualizeFieldContext ) { const states: Record = {}; await Promise.all( Object.entries(datasourceMap).map(([datasourceId, datasource]) => { if (datasourceStates[datasourceId]) { return datasource - .initialize( - datasourceStates[datasourceId].state || undefined, - references, - visualizeTriggerFieldContext - ) + .initialize(datasourceStates[datasourceId].state || undefined, references, initialContext) .then((datasourceState) => { states[datasourceId] = { isLoading: false, state: datasourceState }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index e0c125ed4e7240..3b64111b88c60b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -73,15 +73,20 @@ export function getSuggestions({ const datasourceTableSuggestions = _.flatten( datasources.map(([datasourceId, datasource]) => { const datasourceState = datasourceStates[datasourceId].state; - const dataSourceSuggestions = visualizeTriggerFieldContext - ? datasource.getDatasourceSuggestionsForVisualizeField( - datasourceState, - visualizeTriggerFieldContext.indexPatternId, - visualizeTriggerFieldContext.fieldName - ) - : field - ? datasource.getDatasourceSuggestionsForField(datasourceState, field) - : datasource.getDatasourceSuggestionsFromCurrentState(datasourceState); + let dataSourceSuggestions; + if (visualizeTriggerFieldContext) { + dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeField( + datasourceState, + visualizeTriggerFieldContext.indexPatternId, + visualizeTriggerFieldContext.fieldName + ); + } else if (field) { + dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(datasourceState, field); + } else { + dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState( + datasourceState + ); + } return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId })); }) ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 6f79c73f203149..28cdd5b57559a5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -195,33 +195,22 @@ export function InnerWorkspacePanel({ } }, [expression, localState.expressionBuildError]); - useEffect(() => { - if ( - !activeDatasourceId || - !visualizeTriggerFieldContext || - localState.visualizeTriggerSuggestionsLoaded || - title - ) { - return; - } - + if (visualizeTriggerFieldContext && !localState.visualizeTriggerSuggestionsLoaded && !title) { const suggestions = getSuggestions({ - datasourceMap: { [activeDatasourceId]: datasourceMap[activeDatasourceId] }, + datasourceMap, datasourceStates, visualizationMap, activeVisualizationId, visualizationState, visualizeTriggerFieldContext, }); - if (suggestions.length) { const selectedSuggestion = suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; switchToSuggestion(dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION'); setLocalState((s) => ({ ...s, visualizeTriggerSuggestionsLoaded: true })); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [visualizeTriggerFieldContext, visualizationState]); + } function onDrop() { if (suggestionForDraggedField) { diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index 66d6f1eca26c68..e9f8013ef7e2d1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -53,7 +53,7 @@ describe('editor_frame service', () => { query: { query: '', language: 'lucene' }, filters: [], showNoDataPopover: jest.fn(), - visualizeTriggerFieldContext: { + initialContext: { indexPatternId: '1', fieldName: 'test', }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index ee774eba6be055..474735759d1a4c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -135,7 +135,7 @@ export class EditorFrameService { savedQuery, onChange, showNoDataPopover, - visualizeTriggerFieldContext, + initialContext, } ) => { domElement = element; @@ -163,7 +163,7 @@ export class EditorFrameService { savedQuery={savedQuery} onChange={onChange} showNoDataPopover={showNoDataPopover} - visualizeTriggerFieldContext={visualizeTriggerFieldContext} + initialContext={initialContext} /> , domElement diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 9e2aa78c6f132e..4ca9453eea24ed 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -135,7 +135,7 @@ export function getIndexPatternDatasource({ async initialize( persistedState?: IndexPatternPersistedState, references?: SavedObjectReference[], - visualizeTriggerFieldContext?: VisualizeFieldContext + initialContext?: VisualizeFieldContext ) { return loadInitialState({ persistedState, @@ -144,7 +144,7 @@ export function getIndexPatternDatasource({ defaultIndexPatternId: core.uiSettings.get('defaultIndex'), storage, indexPatternsService, - visualizeTriggerFieldContext, + initialContext, }); }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 629d5bb9f30bbc..7626f7adeba1d5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -1165,49 +1165,6 @@ describe('IndexPattern Data Source suggestions', () => { }) ); }); - - it('should apply a bucketed aggregation for a date field', () => { - const suggestions = getDatasourceSuggestionsForVisualizeField( - stateWithEmptyLayer(), - '1', - 'timestamp' - ); - - expect(suggestions).toContainEqual( - expect.objectContaining({ - state: expect.objectContaining({ - layers: { - previousLayer: expect.objectContaining({ - columnOrder: ['id1', 'id2'], - columns: { - id1: expect.objectContaining({ - operationType: 'date_histogram', - sourceField: 'timestamp', - }), - id2: expect.objectContaining({ - operationType: 'count', - }), - }, - }), - }, - }), - table: { - changeType: 'initial', - label: undefined, - isMultiRow: true, - columns: [ - expect.objectContaining({ - columnId: 'id1', - }), - expect.objectContaining({ - columnId: 'id2', - }), - ], - layerId: 'previousLayer', - }, - }) - ); - }); }); describe('finding the layer that is using the current index pattern', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index ef5e0eafdecb42..06cfdf7e034817 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -447,7 +447,7 @@ describe('loader', () => { savedObjectsClient: mockClient(), indexPatternsService: mockIndexPatternsService(), storage, - visualizeTriggerFieldContext: { + initialContext: { indexPatternId: '1', fieldName: '', }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 10c7544a73c219..fd8e071d524eed 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -180,7 +180,7 @@ export async function loadInitialState({ defaultIndexPatternId, storage, indexPatternsService, - visualizeTriggerFieldContext, + initialContext, }: { persistedState?: IndexPatternPersistedState; references?: SavedObjectReference[]; @@ -188,7 +188,7 @@ export async function loadInitialState({ defaultIndexPatternId?: string; storage: IStorageWrapper; indexPatternsService: IndexPatternsService; - visualizeTriggerFieldContext?: VisualizeFieldContext; + initialContext?: VisualizeFieldContext; }): Promise { const indexPatternRefs = await loadIndexPatternRefs(savedObjectsClient); const lastUsedIndexPatternId = getLastUsedIndexPatternId(storage, indexPatternRefs); @@ -204,15 +204,13 @@ export async function loadInitialState({ : [lastUsedIndexPatternId || defaultIndexPatternId || indexPatternRefs[0].id] ); - const currentIndexPatternId = visualizeTriggerFieldContext?.indexPatternId ?? requiredPatterns[0]; + const currentIndexPatternId = initialContext?.indexPatternId ?? requiredPatterns[0]; setLastUsedIndexPatternId(storage, currentIndexPatternId); const indexPatterns = await loadIndexPatterns({ indexPatternsService, cache: {}, - patterns: visualizeTriggerFieldContext - ? [visualizeTriggerFieldContext.indexPatternId] - : requiredPatterns, + patterns: initialContext ? [initialContext.indexPatternId] : requiredPatterns, }); if (state) { return { diff --git a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts index 3590aac4db1410..a473d433ac89d2 100644 --- a/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts +++ b/x-pack/plugins/lens/public/trigger_actions/visualize_field_actions.ts @@ -22,7 +22,7 @@ export const visualizeFieldAction = (application: ApplicationStart) => isCompatible: async () => !!application.capabilities.visualize.show, execute: async (context: VisualizeFieldContext) => { application.navigateToApp('lens', { - state: context, + state: { type: ACTION_VISUALIZE_LENS_FIELD, payload: context }, }); }, }); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 291f18cd61abea..7966b0adf8f3fb 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -41,7 +41,7 @@ export interface EditorFrameProps { query: Query; filters: Filter[]; savedQuery?: SavedQuery; - visualizeTriggerFieldContext?: VisualizeFieldContext; + initialContext?: VisualizeFieldContext; // Frame loader (app or embeddable) is expected to call this when it loads and updates // This should be replaced with a top-down state @@ -146,7 +146,7 @@ export interface Datasource { initialize: ( state?: P, savedObjectReferences?: SavedObjectReference[], - visualizeTriggerFieldContext?: VisualizeFieldContext + initialContext?: VisualizeFieldContext ) => Promise; // Given the current state, which parts should be saved? From 89c8662e22f501fcd9d9f11f56390d2098a13040 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 29 Sep 2020 12:35:34 +0300 Subject: [PATCH 10/15] unregister visualize action only if the action exists --- x-pack/plugins/lens/public/plugin.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index bd63fe92d5737f..5fed59d6acb58a 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -162,7 +162,9 @@ export class LensPlugin { this.attributeService = getLensAttributeService(core, startDependencies); this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; // unregisters the Visualize action and registers the lens one - startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); + if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { + startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); + } startDependencies.uiActions.addTriggerAction( VISUALIZE_FIELD_TRIGGER, visualizeFieldAction(core.application) From caaed270194166a585ff4be3b9ec4eca5b95c850 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 29 Sep 2020 15:15:54 +0300 Subject: [PATCH 11/15] Change the test to not be flaky --- x-pack/test/functional/apps/discover/visualize_field.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index b559054d82b659..7449faae2f7caa 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -10,6 +10,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const filterBar = getService('filterBar'); const queryBar = getService('queryBar'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects([ 'common', 'error', @@ -17,7 +18,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'timePicker', 'security', 'spaceSelector', - 'lens', 'header', ]); @@ -42,11 +42,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.discover.expectFieldListItemVisualize('bytes'); }); - it('visualizes field to Lens and saves it', async () => { + it('visualizes field to Lens and loads fields to the dimesion editor', async () => { await PageObjects.discover.findFieldByName('bytes'); await PageObjects.discover.clickFieldListItemVisualize('bytes'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.lens.save('VisualizeFieldVis'); + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(await dimensions[1].getVisibleText()).to.be('Average of bytes'); }); it('should preserve app filters in lens', async () => { From 83da4faae413a5e40db99e7d189f63678934b3b2 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 30 Sep 2020 14:56:02 +0300 Subject: [PATCH 12/15] Move suggestions to editor frame and hide the emptyWorkspace for visualize field --- .../editor_frame/editor_frame.tsx | 17 +++ .../editor_frame/suggestion_helpers.ts | 39 +++++ .../workspace_panel/workspace_panel.test.tsx | 137 ------------------ .../workspace_panel/workspace_panel.tsx | 22 +-- .../indexpattern_suggestions.test.tsx | 109 +------------- .../indexpattern_suggestions.ts | 8 +- 6 files changed, 72 insertions(+), 260 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 55e51dde58dd40..d1c5d8a5d7ad35 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -22,6 +22,7 @@ import { Filter, Query, SavedQuery } from '../../../../../../src/plugins/data/pu import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; import { EditorFrameStartPlugins } from '../service'; import { initializeDatasources, createDatasourceLayers } from './state_helpers'; +import { applyVisualizeFieldSuggestions } from './suggestion_helpers'; export interface EditorFrameProps { doc?: Document; @@ -186,6 +187,22 @@ export function EditorFrame(props: EditorFrameProps) { [allLoaded, activeVisualization, state.visualization.state] ); + // Get suggestions for visualize field when all datasources are ready + useEffect(() => { + if (allLoaded && props.initialContext && !props.doc) { + applyVisualizeFieldSuggestions({ + datasourceMap: props.datasourceMap, + datasourceStates: state.datasourceStates, + visualizationMap: props.visualizationMap, + activeVisualizationId: state.visualization.activeId, + visualizationState: state.visualization.state, + visualizeTriggerFieldContext: props.initialContext, + dispatch, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [allLoaded]); + // The frame needs to call onChange every time its internal state changes useEffect( () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index 3b64111b88c60b..c4a92dde6187ce 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -114,6 +114,45 @@ export function getSuggestions({ ).sort((a, b) => b.score - a.score); } +export function applyVisualizeFieldSuggestions({ + datasourceMap, + datasourceStates, + visualizationMap, + activeVisualizationId, + visualizationState, + visualizeTriggerFieldContext, + dispatch, +}: { + datasourceMap: Record; + datasourceStates: Record< + string, + { + isLoading: boolean; + state: unknown; + } + >; + visualizationMap: Record; + activeVisualizationId: string | null; + subVisualizationId?: string; + visualizationState: unknown; + visualizeTriggerFieldContext?: VisualizeFieldContext; + dispatch: (action: Action) => void; +}): void { + const suggestions = getSuggestions({ + datasourceMap, + datasourceStates, + visualizationMap, + activeVisualizationId, + visualizationState, + visualizeTriggerFieldContext, + }); + if (suggestions.length) { + const selectedSuggestion = + suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; + switchToSuggestion(dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION'); + } +} + /** * Queries a single visualization extensions for a single datasource suggestion and * creates an array of complete suggestions containing both the target datasource diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 5c39e0c1e2f647..47e3b41df3b21b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -800,141 +800,4 @@ describe('workspace_panel', () => { }); }); }); - - describe('suggestions from navigating in workspace panel from Discover', () => { - let mockDispatch: jest.Mock; - let frame: jest.Mocked; - - const visualizeTriggerFieldContext = { - indexPatternId: '1', - fieldName: 'timestamp', - }; - - beforeEach(() => { - frame = createMockFramePublicAPI(); - mockDispatch = jest.fn(); - }); - - function initComponent() { - instance = mount( - - ); - } - - it('should immediately transition if exactly one suggestion is returned', () => { - const expectedTable: TableSuggestion = { - isMultiRow: true, - layerId: '1', - columns: [], - changeType: 'unchanged', - }; - mockDatasource.getDatasourceSuggestionsForVisualizeField.mockReturnValueOnce([ - { - state: {}, - table: expectedTable, - keptLayerIds: [], - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'my title', - state: {}, - previewIcon: 'empty', - }, - ]); - initComponent(); - - expect(mockDatasource.getDatasourceSuggestionsForVisualizeField).toHaveBeenCalledTimes(1); - expect(mockVisualization.getSuggestions).toHaveBeenCalledWith( - expect.objectContaining({ - table: expectedTable, - }) - ); - expect(mockDispatch).toHaveBeenCalledWith({ - type: 'SWITCH_VISUALIZATION', - newVisualizationId: 'vis', - initialState: {}, - datasourceState: {}, - datasourceId: 'mock', - }); - }); - - it('should immediately transition to the first suggestion if there are multiple', () => { - mockDatasource.getDatasourceSuggestionsForVisualizeField.mockReturnValueOnce([ - { - state: {}, - table: { - isMultiRow: true, - columns: [], - layerId: '1', - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - { - state: {}, - table: { - isMultiRow: true, - columns: [], - layerId: '1', - changeType: 'unchanged', - }, - keptLayerIds: [], - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.5, - title: 'second suggestion', - state: {}, - previewIcon: 'empty', - }, - ]); - mockVisualization.getSuggestions.mockReturnValueOnce([ - { - score: 0.8, - title: 'first suggestion', - state: { - isFirst: true, - }, - previewIcon: 'empty', - }, - ]); - - initComponent(); - expect(mockDispatch).toHaveBeenCalledWith({ - type: 'SWITCH_VISUALIZATION', - newVisualizationId: 'vis', - initialState: { - isFirst: true, - }, - datasourceState: {}, - datasourceId: 'mock', - }); - }); - }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 28cdd5b57559a5..781c722a37e18f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -110,7 +110,6 @@ export function InnerWorkspacePanel({ const [localState, setLocalState] = useState({ expressionBuildError: undefined as string | undefined, expandError: false, - visualizeTriggerSuggestionsLoaded: false, }); const activeVisualization = activeVisualizationId @@ -195,23 +194,6 @@ export function InnerWorkspacePanel({ } }, [expression, localState.expressionBuildError]); - if (visualizeTriggerFieldContext && !localState.visualizeTriggerSuggestionsLoaded && !title) { - const suggestions = getSuggestions({ - datasourceMap, - datasourceStates, - visualizationMap, - activeVisualizationId, - visualizationState, - visualizeTriggerFieldContext, - }); - if (suggestions.length) { - const selectedSuggestion = - suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; - switchToSuggestion(dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION'); - setLocalState((s) => ({ ...s, visualizeTriggerSuggestionsLoaded: true })); - } - } - function onDrop() { if (suggestionForDraggedField) { trackUiEvent('drop_onto_workspace'); @@ -274,7 +256,9 @@ export function InnerWorkspacePanel({ } function renderVisualization() { - if (expression === null) { + // we don't want to render the emptyWorkspace on visualizing field from Discover + // as it is specific for the drag and drop functionality and can confuse the users + if (expression === null && !visualizeTriggerFieldContext) { return renderEmptyWorkspace(); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 7626f7adeba1d5..f4d993d858998c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -1087,34 +1087,9 @@ describe('IndexPattern Data Source suggestions', () => { }; } - it('should return an empty array', () => { - const suggestions = getDatasourceSuggestionsForVisualizeField( - stateWithoutLayer(), - '1', - 'source' - ); - - expect(suggestions).toEqual([]); - }); - }); - describe('with a previous empty layer', () => { - function stateWithEmptyLayer() { - const state = testInitialState(); - return { - ...state, - layers: { - previousLayer: { - indexPatternId: '1', - columns: {}, - columnOrder: [], - }, - }, - }; - } - it('should return an empty array if the field does not exist', () => { const suggestions = getDatasourceSuggestionsForVisualizeField( - stateWithEmptyLayer(), + stateWithoutLayer(), '1', 'field_not_exist' ); @@ -1124,7 +1099,7 @@ describe('IndexPattern Data Source suggestions', () => { it('should apply a bucketed aggregation for a string field', () => { const suggestions = getDatasourceSuggestionsForVisualizeField( - stateWithEmptyLayer(), + stateWithoutLayer(), '1', 'source' ); @@ -1133,15 +1108,15 @@ describe('IndexPattern Data Source suggestions', () => { expect.objectContaining({ state: expect.objectContaining({ layers: { - previousLayer: expect.objectContaining({ - columnOrder: ['id1', 'id2'], + id1: expect.objectContaining({ + columnOrder: ['id2', 'id3'], columns: { - id1: expect.objectContaining({ + id2: expect.objectContaining({ operationType: 'terms', sourceField: 'source', params: expect.objectContaining({ size: 5 }), }), - id2: expect.objectContaining({ + id3: expect.objectContaining({ operationType: 'count', }), }, @@ -1153,82 +1128,14 @@ describe('IndexPattern Data Source suggestions', () => { label: undefined, isMultiRow: true, columns: [ - expect.objectContaining({ - columnId: 'id1', - }), expect.objectContaining({ columnId: 'id2', }), - ], - layerId: 'previousLayer', - }, - }) - ); - }); - }); - - describe('finding the layer that is using the current index pattern', () => { - function stateWithCurrentIndexPattern(): IndexPatternPrivateState { - const state = testInitialState(); - - return { - ...state, - currentIndexPatternId: '1', - layers: { - previousLayer: { - indexPatternId: '1', - columns: {}, - columnOrder: [], - }, - currentLayer: { - indexPatternId: '2', - columns: {}, - columnOrder: [], - }, - }, - }; - } - - it('suggests on the layer that matches by indexPatternId', () => { - const initialState = stateWithCurrentIndexPattern(); - const suggestions = getDatasourceSuggestionsForVisualizeField( - initialState, - '2', - 'timestamp' - ); - - expect(suggestions).toContainEqual( - expect.objectContaining({ - state: expect.objectContaining({ - layers: { - previousLayer: initialState.layers.previousLayer, - currentLayer: expect.objectContaining({ - columnOrder: ['id1', 'id2'], - columns: { - id1: expect.objectContaining({ - operationType: 'date_histogram', - sourceField: 'timestamp', - }), - id2: expect.objectContaining({ - operationType: 'count', - }), - }, - }), - }, - }), - table: { - changeType: 'initial', - label: undefined, - isMultiRow: true, - columns: [ - expect.objectContaining({ - columnId: 'id1', - }), expect.objectContaining({ - columnId: 'id2', + columnId: 'id3', }), ], - layerId: 'currentLayer', + layerId: 'id1', }, }) ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index 729a9a0dfd968a..892206bbc51697 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -129,9 +129,11 @@ export function getDatasourceSuggestionsForVisualizeField( const indexPattern = state.indexPatterns[indexPatternId]; const field = indexPattern.fields.find((fld) => fld.name === fieldName); - if (layerIds.length === 0 || !field) return []; - const layerId = layerIds[0]; - return getEmptyLayerSuggestionsForField(state, layerId, indexPatternId, field); + if (layerIds.length !== 0 || !field) return []; + const newId = generateId(); + return getEmptyLayerSuggestionsForField(state, newId, indexPatternId, field).concat( + getEmptyLayerSuggestionsForField({ ...state, layers: {} }, newId, indexPatternId, field) + ); } function getBucketOperation(field: IndexPatternField) { From 5e62b427c28ee39545f0ddf450b33f1bd1d567c1 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 1 Oct 2020 10:01:28 +0300 Subject: [PATCH 13/15] Update ui actions docs --- ...ctions-public.action_visualize_lens_field.md | 11 +++++++++++ ...ontextmapping.action_visualize_lens_field.md | 11 +++++++++++ ...ns-ui_actions-public.actioncontextmapping.md | 1 + .../kibana-plugin-plugins-ui_actions-public.md | 1 + ...-public.uiactionsservice.addtriggeraction.md | 2 +- ...actions-public.uiactionsservice.getaction.md | 2 +- ...public.uiactionsservice.gettriggeractions.md | 2 +- ...ctionsservice.gettriggercompatibleactions.md | 2 +- ...lugins-ui_actions-public.uiactionsservice.md | 10 +++++----- ...ns-public.uiactionsservice.registeraction.md | 2 +- src/plugins/ui_actions/public/public.api.md | 17 ++++++++++++----- 11 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_lens_field.md diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md new file mode 100644 index 00000000000000..b00618f5105105 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md) + +## ACTION\_VISUALIZE\_LENS\_FIELD variable + +Signature: + +```typescript +ACTION_VISUALIZE_LENS_FIELD = "ACTION_VISUALIZE_LENS_FIELD" +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_lens_field.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_lens_field.md new file mode 100644 index 00000000000000..96370a07806d32 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_lens_field.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [ActionContextMapping](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md) > [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_lens_field.md) + +## ActionContextMapping.ACTION\_VISUALIZE\_LENS\_FIELD property + +Signature: + +```typescript +[ACTION_VISUALIZE_LENS_FIELD]: VisualizeFieldContext; +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md index 740e6ac63bfbaf..f83632dea0aa91 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md @@ -17,4 +17,5 @@ export interface ActionContextMapping | [""](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.__.md) | BaseContext | | | [ACTION\_VISUALIZE\_FIELD](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_field.md) | VisualizeFieldContext | | | [ACTION\_VISUALIZE\_GEO\_FIELD](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_geo_field.md) | VisualizeFieldContext | | +| [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.action_visualize_lens_field.md) | VisualizeFieldContext | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md index ce4e8c17b9dffb..5e10de4e0f2a56 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md @@ -39,6 +39,7 @@ | --- | --- | | [ACTION\_VISUALIZE\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_field.md) | | | [ACTION\_VISUALIZE\_GEO\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_geo_field.md) | | +| [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md) | | | [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) | | | [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) | | | [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md index 1782eef92442ce..ba9060e01e57de 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md @@ -11,5 +11,5 @@ Signature: ```typescript -readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; +readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md index 0c4584a07b569e..3e433809f94717 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; +readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md index c65a9a992da2e9..83afcab29689d5 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerActions: (triggerId: T) => Action[]; +readonly getTriggerActions: (triggerId: T) => Action[]; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md index 751abe332b08eb..879f5a3d8628ae 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; +readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md index c372eb113d6828..7fade7c4c841b3 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md @@ -21,19 +21,19 @@ export declare class UiActionsService | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [actions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.actions.md) | | ActionRegistry | | -| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | +| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | | [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void | | | [clear](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.clear.md) | | () => void | Removes all registered triggers and actions. | | [detachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.detachaction.md) | | (triggerId: TriggerId, actionId: string) => void | | | [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | | [executionService](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executionservice.md) | | UiActionsExecutionService | | | [fork](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.fork.md) | | () => UiActionsService | "Fork" a separate instance of UiActionsService that inherits all existing triggers and actions, but going forward all new triggers and actions added to this instance of UiActionsService are only available within this instance. | -| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK"> | | +| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK"> | | | [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T> | | -| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">[] | | -| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">[]> | | +| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">[] | | +| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">[]> | | | [hasAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.hasaction.md) | | (actionId: string) => boolean | | -| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK"> | | +| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK"> | | | [registerTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registertrigger.md) | | (trigger: Trigger) => void | | | [triggers](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggers.md) | | TriggerRegistry | | | [triggerToActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggertoactions.md) | | TriggerToActionsRegistry | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md index c71e86fc09dc76..eeda7b503037dd 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; +readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; ``` diff --git a/src/plugins/ui_actions/public/public.api.md b/src/plugins/ui_actions/public/public.api.md index 8b3d81a589365f..229997281db49c 100644 --- a/src/plugins/ui_actions/public/public.api.md +++ b/src/plugins/ui_actions/public/public.api.md @@ -45,6 +45,11 @@ export const ACTION_VISUALIZE_FIELD = "ACTION_VISUALIZE_FIELD"; // @public (undocumented) export const ACTION_VISUALIZE_GEO_FIELD = "ACTION_VISUALIZE_GEO_FIELD"; +// Warning: (ae-missing-release-tag) "ACTION_VISUALIZE_LENS_FIELD" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ACTION_VISUALIZE_LENS_FIELD = "ACTION_VISUALIZE_LENS_FIELD"; + // Warning: (ae-missing-release-tag) "ActionByType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -62,6 +67,8 @@ export interface ActionContextMapping { [ACTION_VISUALIZE_FIELD]: VisualizeFieldContext; // (undocumented) [ACTION_VISUALIZE_GEO_FIELD]: VisualizeFieldContext; + // (undocumented) + [ACTION_VISUALIZE_LENS_FIELD]: VisualizeFieldContext; } // Warning: (ae-missing-release-tag) "ActionDefinitionByType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -225,7 +232,7 @@ export class UiActionsService { // // (undocumented) protected readonly actions: ActionRegistry; - readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; + readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; // (undocumented) readonly attachAction: (triggerId: T, actionId: string) => void; readonly clear: () => void; @@ -239,21 +246,21 @@ export class UiActionsService { readonly executionService: UiActionsExecutionService; readonly fork: () => UiActionsService; // (undocumented) - readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; + readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; // Warning: (ae-forgotten-export) The symbol "TriggerContract" needs to be exported by the entry point index.d.ts // // (undocumented) readonly getTrigger: (triggerId: T) => TriggerContract; // (undocumented) - readonly getTriggerActions: (triggerId: T) => Action[]; + readonly getTriggerActions: (triggerId: T) => Action[]; // (undocumented) - readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; + readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; // (undocumented) readonly hasAction: (actionId: string) => boolean; // Warning: (ae-forgotten-export) The symbol "ActionContext" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; + readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK">; // (undocumented) readonly registerTrigger: (trigger: Trigger) => void; // Warning: (ae-forgotten-export) The symbol "TriggerRegistry" needs to be exported by the entry point index.d.ts From 32dd2bdb4a36b8d96382b9c4d4798d912b0b9b95 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 2 Oct 2020 11:41:55 +0300 Subject: [PATCH 14/15] Add a retry to remove test flakiness --- x-pack/test/functional/apps/discover/visualize_field.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index 7449faae2f7caa..b0e4cb591791b0 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -11,6 +11,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const filterBar = getService('filterBar'); const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); + const retry = getService('retry'); const PageObjects = getPageObjects([ 'common', 'error', @@ -46,9 +47,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.discover.findFieldByName('bytes'); await PageObjects.discover.clickFieldListItemVisualize('bytes'); await PageObjects.header.waitUntilLoadingHasFinished(); - const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); - expect(dimensions).to.have.length(2); - expect(await dimensions[1].getVisibleText()).to.be('Average of bytes'); + await retry.try(async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger'); + expect(dimensions).to.have.length(2); + expect(await dimensions[1].getVisibleText()).to.be('Average of bytes'); + }); }); it('should preserve app filters in lens', async () => { From a27df919941254fa3dd7c8f03cb4fa1ea9ba8c40 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 2 Oct 2020 14:43:52 +0300 Subject: [PATCH 15/15] Fix bug of infinite loader when removing the y axis dimension --- .../editor_frame/editor_frame.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index d1c5d8a5d7ad35..32fd4461dfc8bc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useReducer } from 'react'; +import React, { useEffect, useReducer, useState } from 'react'; import { CoreSetup, CoreStart } from 'kibana/public'; import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { Datasource, FramePublicAPI, Visualization } from '../../types'; @@ -52,6 +52,9 @@ export interface EditorFrameProps { export function EditorFrame(props: EditorFrameProps) { const [state, dispatch] = useReducer(reducer, props, getInitialState); + const [visualizeTriggerFieldContext, setVisualizeTriggerFieldContext] = useState( + props.initialContext + ); const { onError } = props; const activeVisualization = state.visualization.activeId && props.visualizationMap[state.visualization.activeId]; @@ -70,7 +73,7 @@ export function EditorFrame(props: EditorFrameProps) { props.datasourceMap, state.datasourceStates, props.doc?.references, - props.initialContext + visualizeTriggerFieldContext ) .then((result) => { if (!isUnmounted) { @@ -189,16 +192,17 @@ export function EditorFrame(props: EditorFrameProps) { // Get suggestions for visualize field when all datasources are ready useEffect(() => { - if (allLoaded && props.initialContext && !props.doc) { + if (allLoaded && visualizeTriggerFieldContext && !props.doc) { applyVisualizeFieldSuggestions({ datasourceMap: props.datasourceMap, datasourceStates: state.datasourceStates, visualizationMap: props.visualizationMap, activeVisualizationId: state.visualization.activeId, visualizationState: state.visualization.state, - visualizeTriggerFieldContext: props.initialContext, + visualizeTriggerFieldContext, dispatch, }); + setVisualizeTriggerFieldContext(undefined); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [allLoaded]); @@ -298,7 +302,7 @@ export function EditorFrame(props: EditorFrameProps) { ExpressionRenderer={props.ExpressionRenderer} core={props.core} plugins={props.plugins} - visualizeTriggerFieldContext={props.initialContext} + visualizeTriggerFieldContext={visualizeTriggerFieldContext} /> ) }