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 476ca0ec17066..664ea5300bfbf 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 b00f4628ffb96..0be3c19fc1c4d 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 6671d2c981910..e570ed5e49e6a 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 67d9d5ef64483..518a91da2c7aa 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 f9c63f54d6713..06fea82d727c1 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 0000000000000..f666d3cd92ca9 --- /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 06fea82d727c1..4e62371785f43 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 63c2a6b9b2f29..9477f2f985e86 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 bfdf4ceaaabd3..2ef2f6f172b9c 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 ebc38e4929f6c..eafe38d210e26 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 e628ea0675a8d..02811cc5bb275 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 72ad8e074226c..e1ce22136d9ba 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 6deb9ffd37a06..78d695880a216 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 63b8b1f048296..c5c66c1c820e8 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 2bb1baf9d54f2..e0c125ed4e724 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 06cd858eda210..04373a1498a7b 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 86b137851d9bd..93898ef1d43a8 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 7b1d091c1c8fe..cf0ed978dd1a4 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 5fc347179a032..c4bdbb56f70fb 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 3b3750cf7c560..9e2aa78c6f132 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 f3aa9c4f51c82..a81fdd9e1ef48 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 19213d4afc9bc..3cb5555278413 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 0ab658b961336..735177a6d5961 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 f666d3cd92ca9..3590aac4db141 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 edb787d9ec1a1..291f18cd61abe 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 47e3b41df3b21..5c39e0c1e2f64 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 663d7c18bb370..f26f07c65affc 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 a81fdd9e1ef48..757cc5efa81cc 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 9477f2f985e86..baaf5af2461ca 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 2ef2f6f172b9c..b625b8518bae9 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 eafe38d210e26..829e7e278f048 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 f26f07c65affc..629d5bb9f30bb 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 757cc5efa81cc..e29f32fbd9c9a 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 b625b8518bae9..b7a24adc8a687 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 04373a1498a7b..dfef6b390091d 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 759225d80fa20..0cf98c2088a40 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 0000000000000..b559054d82b65 --- /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 67b9482e28152..46a0b56a03ec5 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 199845cbcce0d..ee92c52a3cc76 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 6d7bbb9ca8607..2c2050fd99243 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 7819f3c545a3a..90ba74decfd80 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 7d4f6a933175e..bd5a9b5a8ed0a 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 02811cc5bb275..7328cdaf6fc9b 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 78d695880a216..775e5d337f515 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 e0c125ed4e724..3b64111b88c60 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 6f79c73f20314..28cdd5b57559a 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 66d6f1eca26c6..e9f8013ef7e2d 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 ee774eba6be05..474735759d1a4 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 9e2aa78c6f132..4ca9453eea24e 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 629d5bb9f30bb..7626f7adeba1d 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 ef5e0eafdecb4..06cfdf7e03481 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 10c7544a73c21..fd8e071d524ee 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 3590aac4db141..a473d433ac89d 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 291f18cd61abe..7966b0adf8f3f 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 bd63fe92d5737..5fed59d6acb58 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 b559054d82b65..7449faae2f7ca 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 55e51dde58dd4..d1c5d8a5d7ad3 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 3b64111b88c60..c4a92dde6187c 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 5c39e0c1e2f64..47e3b41df3b21 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 28cdd5b57559a..781c722a37e18 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 7626f7adeba1d..f4d993d858998 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 729a9a0dfd968..892206bbc5169 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 0000000000000..b00618f510510 --- /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 0000000000000..96370a07806d3 --- /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 740e6ac63bfba..f83632dea0aa9 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 ce4e8c17b9dff..5e10de4e0f2a5 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 1782eef92442c..ba9060e01e57d 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 0c4584a07b569..3e433809f9471 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 c65a9a992da2e..83afcab29689d 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 751abe332b08e..879f5a3d8628a 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 c372eb113d682..7fade7c4c841b 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 c71e86fc09dc7..eeda7b503037d 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 8b3d81a589365..229997281db49 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 7449faae2f7ca..b0e4cb591791b 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 d1c5d8a5d7ad3..32fd4461dfc8b 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} /> ) }