From d29f5ede44bb8475b473cf4b8eae3b482697d033 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 9 Oct 2024 14:29:31 +0200 Subject: [PATCH 01/38] [ES|QL] Keep the preferred chart type --- .../public/services/lens_vis_service.ts | 38 ++++++++++++++++++- .../visualizations/xy/visualization.tsx | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index eccfd663b2557..f8243795c2d41 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -19,6 +19,8 @@ import type { Suggestion, TermsIndexPatternColumn, TypedLensByValueInput, + PieVisualizationState, + XYState, } from '@kbn/lens-plugin/public'; import type { AggregateQuery, TimeRange } from '@kbn/es-query'; import { getAggregateQueryMode, isOfAggregateQueryType } from '@kbn/es-query'; @@ -72,6 +74,19 @@ interface LensVisServiceParams { lensSuggestionsApi: LensSuggestionsApi; } +export enum ChartType { + XY = 'XY', + Bar = 'Bar', + Line = 'Line', + Area = 'Area', + Donut = 'Donut', + Heatmap = 'Heat map', + Treemap = 'Treemap', + Tagcloud = 'Tag cloud', + Waffle = 'Waffle', + Table = 'Table', +} + export class LensVisService { private state$: BehaviorSubject; private services: Services; @@ -248,6 +263,7 @@ export class LensVisService { const histogramSuggestionForESQL = this.getHistogramSuggestionForESQL({ queryParams, breakdownField, + preferredVisAttributes: externalVisContext?.attributes, }); if (histogramSuggestionForESQL) { availableSuggestionsWithType.push({ @@ -456,9 +472,11 @@ export class LensVisService { private getHistogramSuggestionForESQL = ({ queryParams, breakdownField, + preferredVisAttributes, }: { queryParams: QueryParams; breakdownField?: DataViewField; + preferredVisAttributes?: UnifiedHistogramVisContext['attributes']; }): Suggestion | undefined => { const { dataView, query, timeRange, columns } = queryParams; const breakdownColumn = breakdownField?.name @@ -503,7 +521,25 @@ export class LensVisService { if (breakdownColumn) { context.textBasedColumns.push(breakdownColumn); } - const suggestions = this.lensSuggestionsApi(context, dataView, ['lnsDatatable']) ?? []; + let preferredChartType = preferredVisAttributes + ? preferredVisAttributes?.visualizationType + : undefined; + + if (preferredChartType === 'lnsXY') { + preferredChartType = (preferredVisAttributes?.state?.visualization as XYState) + ?.preferredSeriesType; + } + if (preferredChartType === 'lnsPie') { + preferredChartType = (preferredVisAttributes?.state?.visualization as PieVisualizationState) + ?.shape; + } + const suggestions = + this.lensSuggestionsApi( + context, + dataView, + ['lnsDatatable'], + preferredChartType as ChartType + ) ?? []; if (suggestions.length) { const suggestion = suggestions[0]; const suggestionVisualizationState = Object.assign({}, suggestion?.visualizationState); diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 64a2ad4fc2754..0971b60116b2f 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -263,7 +263,7 @@ export const getXyVisualization = ({ getDescription, switchVisualizationType(seriesType: string, state: State, layerId?: string) { - const dataLayer = state.layers.find((l) => l.layerId === layerId); + const dataLayer = state.layers.find((l) => l.layerId === layerId) ?? state.layers[0]; if (dataLayer && !isDataLayer(dataLayer)) { throw new Error('Cannot switch series type for non-data layer'); } From 56fb6e015f33f59fde1d5c065de622aab17d37b5 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 18 Oct 2024 10:15:11 +0200 Subject: [PATCH 02/38] Move chart type enum to visualization utils --- packages/kbn-visualization-utils/index.ts | 1 + packages/kbn-visualization-utils/src/types.ts | 13 +++++++++++++ .../public/services/lens_vis_service.ts | 14 +------------- .../plugins/lens/public/lens_suggestions_api.ts | 14 +------------- .../public/functions/visualize_esql.tsx | 15 +-------------- 5 files changed, 17 insertions(+), 40 deletions(-) diff --git a/packages/kbn-visualization-utils/index.ts b/packages/kbn-visualization-utils/index.ts index 5d4dfecc0ae29..fa4a2fa1b6f55 100644 --- a/packages/kbn-visualization-utils/index.ts +++ b/packages/kbn-visualization-utils/index.ts @@ -11,3 +11,4 @@ export { getTimeZone } from './src/get_timezone'; export { getLensAttributesFromSuggestion } from './src/get_lens_attributes'; export { TooltipWrapper } from './src/tooltip_wrapper'; export { useDebouncedValue } from './src/debounced_value'; +export { ChartType } from './src/types'; diff --git a/packages/kbn-visualization-utils/src/types.ts b/packages/kbn-visualization-utils/src/types.ts index 0337c3349332b..8931ff289ad51 100644 --- a/packages/kbn-visualization-utils/src/types.ts +++ b/packages/kbn-visualization-utils/src/types.ts @@ -43,3 +43,16 @@ export interface Suggestion { changeType: TableChangeType; keptLayerIds: string[]; } + +export enum ChartType { + XY = 'XY', + Bar = 'Bar', + Line = 'Line', + Area = 'Area', + Donut = 'Donut', + Heatmap = 'Heat map', + Treemap = 'Treemap', + Tagcloud = 'Tag cloud', + Waffle = 'Waffle', + Table = 'Table', +} diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index ff3720d6b2588..e774415c7900d 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -30,6 +30,7 @@ import type { AggregateQuery, TimeRange } from '@kbn/es-query'; import { getAggregateQueryMode, isOfAggregateQueryType } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; +import type { ChartType } from '@kbn/visualization-utils'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { XYConfiguration } from '@kbn/visualizations-plugin/common'; import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; @@ -78,19 +79,6 @@ interface LensVisServiceParams { lensSuggestionsApi: LensSuggestionsApi; } -export enum ChartType { - XY = 'XY', - Bar = 'Bar', - Line = 'Line', - Area = 'Area', - Donut = 'Donut', - Heatmap = 'Heat map', - Treemap = 'Treemap', - Tagcloud = 'Tag cloud', - Waffle = 'Waffle', - Table = 'Table', -} - export class LensVisService { private state$: BehaviorSubject; private services: Services; diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 3bdadbf337227..8270922b48a63 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -6,23 +6,11 @@ */ import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import type { ChartType } from '@kbn/visualization-utils'; import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_helpers'; import type { DatasourceMap, VisualizationMap, VisualizeEditorContext } from './types'; import type { DataViewsState } from './state_management'; -export enum ChartType { - XY = 'XY', - Bar = 'Bar', - Line = 'Line', - Area = 'Area', - Donut = 'Donut', - Heatmap = 'Heat map', - Treemap = 'Treemap', - Tagcloud = 'Tag cloud', - Waffle = 'Waffle', - Table = 'Table', -} - interface SuggestionsApiProps { context: VisualizeFieldContext | VisualizeEditorContext; dataView: DataView; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx index e1889c7bc199a..a570d4ba0276a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx @@ -37,7 +37,7 @@ import { VisualizeESQLUserIntention, } from '@kbn/observability-ai-assistant-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; +import { getLensAttributesFromSuggestion, ChartType } from '@kbn/visualization-utils'; import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import ReactDOM from 'react-dom'; import useAsync from 'react-use/lib/useAsync'; @@ -48,19 +48,6 @@ import type { } from '../../common/functions/visualize_esql'; import { ObservabilityAIAssistantAppPluginStartDependencies } from '../types'; -enum ChartType { - XY = 'XY', - Bar = 'Bar', - Line = 'Line', - Area = 'Area', - Donut = 'Donut', - Heatmap = 'Heat map', - Treemap = 'Treemap', - Tagcloud = 'Tag cloud', - Waffle = 'Waffle', - Table = 'Table', -} - interface VisualizeESQLProps { /** Lens start contract, get the ES|QL charts suggestions api */ lens: LensPublicStart; From a391020731507bc11a0a6380317fbbd9e383ea96 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 18 Oct 2024 10:39:46 +0200 Subject: [PATCH 03/38] Keep preferred type in transformational commands too --- .../public/services/lens_vis_service.ts | 42 +++++++++---------- .../public/utils/get_preferred_chart_type.ts | 24 +++++++++++ 2 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index e774415c7900d..bb54702d1b365 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -23,14 +23,11 @@ import type { Suggestion, TermsIndexPatternColumn, TypedLensByValueInput, - PieVisualizationState, - XYState, } from '@kbn/lens-plugin/public'; import type { AggregateQuery, TimeRange } from '@kbn/es-query'; import { getAggregateQueryMode, isOfAggregateQueryType } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; -import type { ChartType } from '@kbn/visualization-utils'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { XYConfiguration } from '@kbn/visualizations-plugin/common'; import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; @@ -50,6 +47,7 @@ import { computeInterval } from '../utils/compute_interval'; import { fieldSupportsBreakdown } from '../utils/field_supports_breakdown'; import { shouldDisplayHistogram } from '../layout/helpers'; import { enrichLensAttributesWithTablesData } from '../utils/lens_vis_from_table'; +import { getPreferredChartType } from '../utils/get_preferred_chart_type'; const UNIFIED_HISTOGRAM_LAYER_ID = 'unifiedHistogram'; @@ -150,7 +148,10 @@ export class LensVisService { externalVisContextStatus: UnifiedHistogramExternalVisContextStatus ) => void; }) => { - const allSuggestions = this.getAllSuggestions({ queryParams }); + const allSuggestions = this.getAllSuggestions({ + queryParams, + preferredVisAttributes: externalVisContext?.attributes, + }); const suggestionState = this.getCurrentSuggestionState({ externalVisContext, @@ -516,25 +517,12 @@ export class LensVisService { if (breakdownColumn) { context.textBasedColumns.push(breakdownColumn); } - let preferredChartType = preferredVisAttributes - ? preferredVisAttributes?.visualizationType + const preferredChartType = preferredVisAttributes + ? getPreferredChartType(preferredVisAttributes) : undefined; - if (preferredChartType === 'lnsXY') { - preferredChartType = (preferredVisAttributes?.state?.visualization as XYState) - ?.preferredSeriesType; - } - if (preferredChartType === 'lnsPie') { - preferredChartType = (preferredVisAttributes?.state?.visualization as PieVisualizationState) - ?.shape; - } const suggestions = - this.lensSuggestionsApi( - context, - dataView, - ['lnsDatatable'], - preferredChartType as ChartType - ) ?? []; + this.lensSuggestionsApi(context, dataView, ['lnsDatatable'], preferredChartType) ?? []; if (suggestions.length) { const suggestion = suggestions[0]; const suggestionVisualizationState = Object.assign({}, suggestion?.visualizationState); @@ -598,9 +586,19 @@ export class LensVisService { ); }; - private getAllSuggestions = ({ queryParams }: { queryParams: QueryParams }): Suggestion[] => { + private getAllSuggestions = ({ + queryParams, + preferredVisAttributes, + }: { + queryParams: QueryParams; + preferredVisAttributes?: UnifiedHistogramVisContext['attributes']; + }): Suggestion[] => { const { dataView, columns, query, isPlainRecord } = queryParams; + const preferredChartType = preferredVisAttributes + ? getPreferredChartType(preferredVisAttributes) + : undefined; + const context = { dataViewSpec: dataView?.toSpec(), fieldName: '', @@ -608,7 +606,7 @@ export class LensVisService { query: query && isOfAggregateQueryType(query) ? query : undefined, }; const allSuggestions = isPlainRecord - ? this.lensSuggestionsApi(context, dataView, ['lnsDatatable']) ?? [] + ? this.lensSuggestionsApi(context, dataView, ['lnsDatatable'], preferredChartType) ?? [] : []; return allSuggestions; diff --git a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts new file mode 100644 index 0000000000000..b3570772e0ae6 --- /dev/null +++ b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import type { PieVisualizationState, XYState } from '@kbn/lens-plugin/public'; +import type { ChartType } from '@kbn/visualization-utils'; +import type { UnifiedHistogramVisContext } from '../types'; + +export const getPreferredChartType = (visAttributes: UnifiedHistogramVisContext['attributes']) => { + let preferredChartType = visAttributes ? visAttributes?.visualizationType : undefined; + + if (preferredChartType === 'lnsXY') { + preferredChartType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; + } + if (preferredChartType === 'lnsPie') { + preferredChartType = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; + } + + return preferredChartType as ChartType; +}; From 6ba9ae5533a5646a3e43efa6fd8b47d92e613ccc Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 18 Oct 2024 12:41:15 +0200 Subject: [PATCH 04/38] Fix ChartType imports --- x-pack/plugins/lens/public/lens_suggestions_api.test.ts | 3 ++- x-pack/plugins/lens/public/plugin.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api.test.ts index 80d2f7a71f6ee..9b5fdacc1aa17 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.test.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.test.ts @@ -6,9 +6,10 @@ */ import type { DataView } from '@kbn/data-views-plugin/public'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; +import { ChartType } from '@kbn/visualization-utils'; import { createMockVisualization, DatasourceMock, createMockDatasource } from './mocks'; import { DatasourceSuggestion } from './types'; -import { suggestionsApi, ChartType } from './lens_suggestions_api'; +import { suggestionsApi } from './lens_suggestions_api'; const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSuggestion => ({ state, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index b2293ea43b109..81d6ecdfa3033 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -62,6 +62,7 @@ import { ContentManagementPublicStart, } from '@kbn/content-management-plugin/public'; import { i18n } from '@kbn/i18n'; +import type { ChartType } from '@kbn/visualization-utils'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; @@ -137,7 +138,6 @@ import { } from '../common/content_management'; import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; import { savedObjectToEmbeddableAttributes } from './lens_attribute_service'; -import { ChartType } from './lens_suggestions_api'; export type { SaveProps } from './app_plugin'; From a6e712e631cbf4f34f47fa105526018390d603c7 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 22 Oct 2024 10:06:20 +0200 Subject: [PATCH 05/38] Adds a unit test --- .../utils/get_preferred_chart_type.test.ts | 43 +++++++++++++++++++ .../public/utils/get_preferred_chart_type.ts | 6 ++- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts diff --git a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts new file mode 100644 index 0000000000000..40b2e787d5107 --- /dev/null +++ b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import type { UnifiedHistogramVisContext } from '../types'; +import { getPreferredChartType } from './get_preferred_chart_type'; + +describe('getPreferredChartType', () => { + it('should return the correct type if the viz type is not XY or partition chart', () => { + const attributes = { + visualizationType: 'lnsHeatmap', + } as UnifiedHistogramVisContext['attributes']; + expect(getPreferredChartType(attributes)).toEqual('Heatmap'); + }); + + it('should return the correct type if the viz type is a partition chart', () => { + const attributes = { + visualizationType: 'lnsPie', + state: { + visualization: { + shape: 'donut', + }, + }, + } as UnifiedHistogramVisContext['attributes']; + expect(getPreferredChartType(attributes)).toEqual('donut'); + }); + + it('should return the correct type if the viz type is an XY chart', () => { + const attributes = { + visualizationType: 'lnsXY', + state: { + visualization: { + preferredSeriesType: 'line', + }, + }, + } as UnifiedHistogramVisContext['attributes']; + expect(getPreferredChartType(attributes)).toEqual('line'); + }); +}); diff --git a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts index b3570772e0ae6..b881d6f359124 100644 --- a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts +++ b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts @@ -10,8 +10,10 @@ import type { PieVisualizationState, XYState } from '@kbn/lens-plugin/public'; import type { ChartType } from '@kbn/visualization-utils'; import type { UnifiedHistogramVisContext } from '../types'; +const LENS_PREFIX = 'lns'; + export const getPreferredChartType = (visAttributes: UnifiedHistogramVisContext['attributes']) => { - let preferredChartType = visAttributes ? visAttributes?.visualizationType : undefined; + let preferredChartType = visAttributes?.visualizationType; if (preferredChartType === 'lnsXY') { preferredChartType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; @@ -20,5 +22,5 @@ export const getPreferredChartType = (visAttributes: UnifiedHistogramVisContext[ preferredChartType = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; } - return preferredChartType as ChartType; + return preferredChartType.replace(LENS_PREFIX, '') as ChartType; }; From dcba4b30846888f20c86a833a14ab082897d4536 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 22 Oct 2024 14:10:26 +0200 Subject: [PATCH 06/38] Add a function description --- .../public/utils/get_preferred_chart_type.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts index b881d6f359124..5f923a1b84042 100644 --- a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts +++ b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts @@ -12,6 +12,11 @@ import type { UnifiedHistogramVisContext } from '../types'; const LENS_PREFIX = 'lns'; +/* + * This function is used to get the preferred chart type from the lens visAttributes. + * For XY and Pie visualizations, the preferred chart type is stored in the state. + * So for example, an XY chart can be area, line, stacked_bar, etc. and a Pie chart can be donut, pie, etc. + */ export const getPreferredChartType = (visAttributes: UnifiedHistogramVisContext['attributes']) => { let preferredChartType = visAttributes?.visualizationType; From 8bc63895cfbb8d203bf289d7b9f7f75c83882316 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Oct 2024 11:12:51 +0200 Subject: [PATCH 07/38] Adds a FT --- .../apps/discover/esql/_esql_view.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index 31e89ac42f3ea..7b0504b6c20f3 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -676,6 +676,42 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB | where countB > 0\nAND \`geo.dest\`=="BT"` ); }); + + it('should append a where clause by clicking the table without changing the chart type', async () => { + await discover.selectTextBaseLang(); + const testQuery = `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB`; + await monacoEditor.setCodeEditorValue(testQuery); + + await testSubjects.click('querySubmitButton'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await unifiedFieldList.waitUntilSidebarHasLoaded(); + + // change the type to line + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); + await header.waitUntilLoadingHasFinished(); + await testSubjects.click('lnsChartSwitchPopover'); + await testSubjects.click('lnsChartSwitchPopover_line'); + await header.waitUntilLoadingHasFinished(); + await testSubjects.click('applyFlyoutButton'); + + await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 1); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await unifiedFieldList.waitUntilSidebarHasLoaded(); + + const editorValue = await monacoEditor.getCodeEditorValue(); + expect(editorValue).to.eql( + `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB\n| WHERE \`geo.dest\`=="BT"` + ); + + // check that the type is still line + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); + await header.waitUntilLoadingHasFinished(); + const chartSwitcher = await testSubjects.find('lnsChartSwitchPopover'); + const type = await chartSwitcher.getVisibleText(); + expect(type).to.be('Line'); + }); }); describe('histogram breakdown', () => { From 484beb7915932de6b5ec68d1b32ef1b63c7e305e Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Oct 2024 12:40:37 +0200 Subject: [PATCH 08/38] Fixes lens suggestion --- x-pack/plugins/lens/public/lens_suggestions_api.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 8270922b48a63..50a5b58f52c6d 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -72,7 +72,9 @@ export const suggestionsApi = ({ // to return line / area instead of a bar chart const chartType = preferredChartType?.toLowerCase(); const XYSuggestion = suggestions.find((sug) => sug.visualizationId === 'lnsXY'); - if (XYSuggestion && chartType && ['area', 'line'].includes(chartType)) { + // console.log(['area', 'line'].some((type) => chartType.includes(type))); + const isAreaOrLine = ['area', 'line'].some((type) => chartType?.includes(type)); + if (XYSuggestion && chartType && isAreaOrLine) { const visualizationState = visualizationMap[ XYSuggestion.visualizationId ]?.switchVisualizationType?.(chartType, XYSuggestion?.visualizationState); From a15c58364ade44cba518c823927b15aa7242532d Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Oct 2024 12:41:53 +0200 Subject: [PATCH 09/38] Cleanup --- x-pack/plugins/lens/public/lens_suggestions_api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 50a5b58f52c6d..5c0c4ed77693b 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -72,7 +72,7 @@ export const suggestionsApi = ({ // to return line / area instead of a bar chart const chartType = preferredChartType?.toLowerCase(); const XYSuggestion = suggestions.find((sug) => sug.visualizationId === 'lnsXY'); - // console.log(['area', 'line'].some((type) => chartType.includes(type))); + // a type can be area, line, area_stacked, area_percentage etc const isAreaOrLine = ['area', 'line'].some((type) => chartType?.includes(type)); if (XYSuggestion && chartType && isAreaOrLine) { const visualizationState = visualizationMap[ From 6f9e0fd018bb151ace698f3332df4f930decd5a2 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 23 Oct 2024 16:03:45 +0200 Subject: [PATCH 10/38] [ES|QL] Keep vis config --- .../public/services/lens_vis_service.ts | 16 +++- .../lens/public/lens_suggestions_api.ts | 94 +++++++++++++++++-- x-pack/plugins/lens/public/plugin.ts | 13 ++- 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index bb54702d1b365..feafdf6d336a8 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -522,7 +522,13 @@ export class LensVisService { : undefined; const suggestions = - this.lensSuggestionsApi(context, dataView, ['lnsDatatable'], preferredChartType) ?? []; + this.lensSuggestionsApi( + context, + dataView, + ['lnsDatatable'], + preferredChartType, + preferredVisAttributes + ) ?? []; if (suggestions.length) { const suggestion = suggestions[0]; const suggestionVisualizationState = Object.assign({}, suggestion?.visualizationState); @@ -606,7 +612,13 @@ export class LensVisService { query: query && isOfAggregateQueryType(query) ? query : undefined, }; const allSuggestions = isPlainRecord - ? this.lensSuggestionsApi(context, dataView, ['lnsDatatable'], preferredChartType) ?? [] + ? this.lensSuggestionsApi( + context, + dataView, + ['lnsDatatable'], + preferredChartType, + preferredVisAttributes + ) ?? [] : []; return allSuggestions; diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 5c0c4ed77693b..cc37f06c21c3e 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -8,8 +8,9 @@ import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ChartType } from '@kbn/visualization-utils'; import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_helpers'; -import type { DatasourceMap, VisualizationMap, VisualizeEditorContext } from './types'; +import type { DatasourceMap, VisualizationMap, VisualizeEditorContext, Suggestion } from './types'; import type { DataViewsState } from './state_management'; +import type { TypedLensByValueInput } from './embeddable/embeddable_component'; interface SuggestionsApiProps { context: VisualizeFieldContext | VisualizeEditorContext; @@ -18,6 +19,61 @@ interface SuggestionsApiProps { datasourceMap?: DatasourceMap; excludedVisualizations?: string[]; preferredChartType?: ChartType; + preferredVisAttributes?: TypedLensByValueInput['attributes']; +} + +function mergeSuggestionWithVisContext({ + suggestion, + visAttributes, + context, +}: { + suggestion: Suggestion; + visAttributes: TypedLensByValueInput['attributes']; + context: VisualizeFieldContext | VisualizeEditorContext; +}): Suggestion { + if ( + visAttributes.visualizationType !== suggestion.visualizationId || + !('textBasedColumns' in context) + ) { + return suggestion; + } + + // it should be one of 'formBased'/'textBased' and have value + const datasourceId: 'formBased' | 'textBased' | undefined = [ + 'formBased' as const, + 'textBased' as const, + ].find((key) => Boolean(visAttributes.state.datasourceStates[key])); + + // if the datasource is formBased, we should not merge + if (!datasourceId || datasourceId === 'formBased') { + return suggestion; + } + const datasourceState = Object.assign({}, visAttributes.state.datasourceStates[datasourceId]); + + // should be based on same columns + if ( + !datasourceState?.layers || + Object.values(datasourceState?.layers).some((layer) => + layer.columns?.some( + // unknown column + (c: { fieldName: string }) => + !context?.textBasedColumns?.find((col) => col.name === c.fieldName) + ) + ) + ) { + return suggestion; + } + + try { + return { + ...suggestion, + datasourceState, + visualizationState: visAttributes.state.visualization, + datasourceId, + }; + } catch { + return suggestion; + } } export const suggestionsApi = ({ @@ -27,6 +83,7 @@ export const suggestionsApi = ({ visualizationMap, excludedVisualizations, preferredChartType, + preferredVisAttributes, }: SuggestionsApiProps) => { const initialContext = context; if (!datasourceMap || !visualizationMap || !dataView.id) return undefined; @@ -72,22 +129,32 @@ export const suggestionsApi = ({ // to return line / area instead of a bar chart const chartType = preferredChartType?.toLowerCase(); const XYSuggestion = suggestions.find((sug) => sug.visualizationId === 'lnsXY'); + console.dir(suggestions); // a type can be area, line, area_stacked, area_percentage etc const isAreaOrLine = ['area', 'line'].some((type) => chartType?.includes(type)); if (XYSuggestion && chartType && isAreaOrLine) { const visualizationState = visualizationMap[ XYSuggestion.visualizationId ]?.switchVisualizationType?.(chartType, XYSuggestion?.visualizationState); + + const updatedSuggestion = { + ...XYSuggestion, + visualizationState, + }; + return [ - { - ...XYSuggestion, - visualizationState, - }, + preferredVisAttributes + ? mergeSuggestionWithVisContext({ + suggestion: updatedSuggestion, + visAttributes: preferredVisAttributes, + context, + }) + : updatedSuggestion, ]; } // in case the user asks for another type (except from area, line) check if it exists // in suggestions and return this instead - if (suggestions.length > 1 && preferredChartType) { + if (suggestions.length > 1 && preferredChartType && !preferredVisAttributes) { const suggestionFromModel = suggestions.find( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); @@ -95,6 +162,21 @@ export const suggestionsApi = ({ return [suggestionFromModel]; } } + + if (suggestions.length > 1 && preferredChartType && preferredVisAttributes) { + const suggestionFromModel = suggestions.find( + (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) + ); + if (suggestionFromModel) { + return [ + mergeSuggestionWithVisContext({ + suggestion: suggestionFromModel, + visAttributes: preferredVisAttributes, + context, + }), + ]; + } + } const activeVisualization = suggestions[0]; if ( activeVisualization.incomplete || diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 81d6ecdfa3033..3145606abaf6c 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -138,6 +138,7 @@ import { } from '../common/content_management'; import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; import { savedObjectToEmbeddableAttributes } from './lens_attribute_service'; +import type { TypedLensByValueInput } from './embeddable/embeddable_component'; export type { SaveProps } from './app_plugin'; @@ -281,7 +282,8 @@ export type LensSuggestionsApi = ( context: VisualizeFieldContext | VisualizeEditorContext, dataViews: DataView, excludedVisualizations?: string[], - preferredChartType?: ChartType + preferredChartType?: ChartType, + preferredVisAttributes?: TypedLensByValueInput['attributes'] ) => Suggestion[] | undefined; export class LensPlugin { @@ -713,7 +715,13 @@ export class LensPlugin { return { formula: createFormulaPublicApi(), chartInfo: createChartInfoApi(startDependencies.dataViews, this.editorFrameService), - suggestions: (context, dataView, excludedVisualizations, preferredChartType) => { + suggestions: ( + context, + dataView, + excludedVisualizations, + preferredChartType, + preferredVisAttributes + ) => { return suggestionsApi({ datasourceMap, visualizationMap, @@ -721,6 +729,7 @@ export class LensPlugin { dataView, excludedVisualizations, preferredChartType, + preferredVisAttributes, }); }, }; From 810bc1915d34c5cf56584ab3cb361f3fe496c087 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 24 Oct 2024 09:44:15 +0200 Subject: [PATCH 11/38] Improves unit test --- .../lens/public/lens_suggestions_api.test.ts | 3 ++ .../lens/public/lens_suggestions_api.ts | 46 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api.test.ts index 9b5fdacc1aa17..b4461d6dfb500 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.test.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.test.ts @@ -265,6 +265,9 @@ describe('suggestionsApi', () => { datasourceMap.textBased.getDatasourceSuggestionsForVisualizeField.mockReturnValue([ generateSuggestion(), ]); + datasourceMap.textBased.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ + generateSuggestion(), + ]); const context = { dataViewSpec: { id: 'index1', diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 5c0c4ed77693b..b7ce399b33a62 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -67,11 +67,33 @@ export const suggestionsApi = ({ dataViews, }); if (!suggestions.length) return []; + const activeVisualization = suggestions[0]; + if ( + activeVisualization.incomplete || + excludedVisualizations?.includes(activeVisualization.visualizationId) + ) { + return []; + } + // compute the rest suggestions depending on the active one and filter out the lnsLegacyMetric + const newSuggestions = getSuggestions({ + datasourceMap, + datasourceStates: { + textBased: { + isLoading: false, + state: activeVisualization.datasourceState, + }, + }, + visualizationMap, + activeVisualization: visualizationMap[activeVisualization.visualizationId], + visualizationState: activeVisualization.visualizationState, + dataViews, + }).filter((sug) => !sug.hide && sug.visualizationId !== 'lnsLegacyMetric'); + // check if there is an XY chart suggested // if user has requested for a line or area, we want to sligthly change the state // to return line / area instead of a bar chart const chartType = preferredChartType?.toLowerCase(); - const XYSuggestion = suggestions.find((sug) => sug.visualizationId === 'lnsXY'); + const XYSuggestion = newSuggestions.find((s) => s.visualizationId === 'lnsXY'); // a type can be area, line, area_stacked, area_percentage etc const isAreaOrLine = ['area', 'line'].some((type) => chartType?.includes(type)); if (XYSuggestion && chartType && isAreaOrLine) { @@ -95,27 +117,7 @@ export const suggestionsApi = ({ return [suggestionFromModel]; } } - const activeVisualization = suggestions[0]; - if ( - activeVisualization.incomplete || - excludedVisualizations?.includes(activeVisualization.visualizationId) - ) { - return []; - } - // compute the rest suggestions depending on the active one and filter out the lnsLegacyMetric - const newSuggestions = getSuggestions({ - datasourceMap, - datasourceStates: { - textBased: { - isLoading: false, - state: activeVisualization.datasourceState, - }, - }, - visualizationMap, - activeVisualization: visualizationMap[activeVisualization.visualizationId], - visualizationState: activeVisualization.visualizationState, - dataViews, - }).filter((sug) => !sug.hide && sug.visualizationId !== 'lnsLegacyMetric'); + const suggestionsList = [activeVisualization, ...newSuggestions]; // if there is no preference from the user, send everything From baa6658440eb24f6dc7ffd747154300a42194a6b Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 24 Oct 2024 12:05:13 +0200 Subject: [PATCH 12/38] Fix bugs --- packages/kbn-visualization-utils/src/types.ts | 2 + .../public/services/lens_vis_service.ts | 34 +++++++++++---- .../public/utils/external_vis_context.ts | 31 +++++++++++++ .../utils/get_preferred_chart_type.test.ts | 43 ------------------- .../public/utils/get_preferred_chart_type.ts | 31 ------------- .../lens/public/lens_suggestions_api.ts | 28 +++++------- 6 files changed, 69 insertions(+), 100 deletions(-) delete mode 100644 src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts delete mode 100644 src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts diff --git a/packages/kbn-visualization-utils/src/types.ts b/packages/kbn-visualization-utils/src/types.ts index 8931ff289ad51..493d52472e867 100644 --- a/packages/kbn-visualization-utils/src/types.ts +++ b/packages/kbn-visualization-utils/src/types.ts @@ -54,5 +54,7 @@ export enum ChartType { Treemap = 'Treemap', Tagcloud = 'Tag cloud', Waffle = 'Waffle', + Pie = 'Pie', + Mosaic = 'Mosaic', Table = 'Table', } diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index feafdf6d336a8..7ff53ae911b4e 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -27,7 +27,7 @@ import type { import type { AggregateQuery, TimeRange } from '@kbn/es-query'; import { getAggregateQueryMode, isOfAggregateQueryType } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; +import { getLensAttributesFromSuggestion, type ChartType } from '@kbn/visualization-utils'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { XYConfiguration } from '@kbn/visualizations-plugin/common'; import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; @@ -42,14 +42,15 @@ import { isSuggestionShapeAndVisContextCompatible, deriveLensSuggestionFromLensAttributes, type QueryParams, + assingQueryToLensLayers, } from '../utils/external_vis_context'; import { computeInterval } from '../utils/compute_interval'; import { fieldSupportsBreakdown } from '../utils/field_supports_breakdown'; import { shouldDisplayHistogram } from '../layout/helpers'; import { enrichLensAttributesWithTablesData } from '../utils/lens_vis_from_table'; -import { getPreferredChartType } from '../utils/get_preferred_chart_type'; const UNIFIED_HISTOGRAM_LAYER_ID = 'unifiedHistogram'; +const LENS_PREFIX = 'lns'; const stateSelectorFactory = (state$: Observable) => @@ -517,8 +518,20 @@ export class LensVisService { if (breakdownColumn) { context.textBasedColumns.push(breakdownColumn); } - const preferredChartType = preferredVisAttributes - ? getPreferredChartType(preferredVisAttributes) + // const preferredChartType = preferredVisAttributes + // ? getPreferredChartType(preferredVisAttributes) + // : undefined; + + const preferredChartType = preferredVisAttributes?.visualizationType.replace( + LENS_PREFIX, + '' + ) as ChartType; + + // here the attributes contain the main query and not the histogram one + const updatedAttributesWithQuery = preferredVisAttributes + ? assingQueryToLensLayers(preferredVisAttributes, { + esql: esqlQuery, + }) : undefined; const suggestions = @@ -527,7 +540,7 @@ export class LensVisService { dataView, ['lnsDatatable'], preferredChartType, - preferredVisAttributes + updatedAttributesWithQuery ) ?? []; if (suggestions.length) { const suggestion = suggestions[0]; @@ -601,9 +614,14 @@ export class LensVisService { }): Suggestion[] => { const { dataView, columns, query, isPlainRecord } = queryParams; - const preferredChartType = preferredVisAttributes - ? getPreferredChartType(preferredVisAttributes) - : undefined; + // const preferredChartType = preferredVisAttributes + // ? getPreferredChartType(preferredVisAttributes) + // : undefined; + + const preferredChartType = preferredVisAttributes?.visualizationType.replace( + LENS_PREFIX, + '' + ) as ChartType; const context = { dataViewSpec: dataView?.toSpec(), diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.ts index fd516dd2c32d8..79b74d263b4ed 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.ts @@ -103,6 +103,37 @@ export const isSuggestionShapeAndVisContextCompatible = ( ); }; +export const assingQueryToLensLayers = ( + visAttributes: UnifiedHistogramVisContext['attributes'], + query: AggregateQuery +) => { + const datasourceId: 'formBased' | 'textBased' | undefined = [ + 'formBased' as const, + 'textBased' as const, + ].find((key) => Boolean(visAttributes.state.datasourceStates[key])); + + // if the datasource is formBased, we should not fix the query + if (!datasourceId || datasourceId === 'formBased') { + return visAttributes; + } + const datasourceState = Object.assign({}, visAttributes.state.datasourceStates[datasourceId]); + + Object.values(datasourceState.layers).forEach((layer) => { + layer.query = query; + }); + + return { + ...visAttributes, + state: { + ...visAttributes.state, + datasourceStates: { + ...visAttributes.state.datasourceStates, + [datasourceId]: datasourceState, + }, + }, + }; +}; + export function deriveLensSuggestionFromLensAttributes({ externalVisContext, queryParams, diff --git a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts deleted file mode 100644 index 40b2e787d5107..0000000000000 --- a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ -import type { UnifiedHistogramVisContext } from '../types'; -import { getPreferredChartType } from './get_preferred_chart_type'; - -describe('getPreferredChartType', () => { - it('should return the correct type if the viz type is not XY or partition chart', () => { - const attributes = { - visualizationType: 'lnsHeatmap', - } as UnifiedHistogramVisContext['attributes']; - expect(getPreferredChartType(attributes)).toEqual('Heatmap'); - }); - - it('should return the correct type if the viz type is a partition chart', () => { - const attributes = { - visualizationType: 'lnsPie', - state: { - visualization: { - shape: 'donut', - }, - }, - } as UnifiedHistogramVisContext['attributes']; - expect(getPreferredChartType(attributes)).toEqual('donut'); - }); - - it('should return the correct type if the viz type is an XY chart', () => { - const attributes = { - visualizationType: 'lnsXY', - state: { - visualization: { - preferredSeriesType: 'line', - }, - }, - } as UnifiedHistogramVisContext['attributes']; - expect(getPreferredChartType(attributes)).toEqual('line'); - }); -}); diff --git a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts b/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts deleted file mode 100644 index 5f923a1b84042..0000000000000 --- a/src/plugins/unified_histogram/public/utils/get_preferred_chart_type.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ -import type { PieVisualizationState, XYState } from '@kbn/lens-plugin/public'; -import type { ChartType } from '@kbn/visualization-utils'; -import type { UnifiedHistogramVisContext } from '../types'; - -const LENS_PREFIX = 'lns'; - -/* - * This function is used to get the preferred chart type from the lens visAttributes. - * For XY and Pie visualizations, the preferred chart type is stored in the state. - * So for example, an XY chart can be area, line, stacked_bar, etc. and a Pie chart can be donut, pie, etc. - */ -export const getPreferredChartType = (visAttributes: UnifiedHistogramVisContext['attributes']) => { - let preferredChartType = visAttributes?.visualizationType; - - if (preferredChartType === 'lnsXY') { - preferredChartType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; - } - if (preferredChartType === 'lnsPie') { - preferredChartType = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; - } - - return preferredChartType.replace(LENS_PREFIX, '') as ChartType; -}; diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 91002c249954f..003e2fae47f71 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -21,7 +21,7 @@ interface SuggestionsApiProps { preferredChartType?: ChartType; preferredVisAttributes?: TypedLensByValueInput['attributes']; } - +// ToDo: Move to a new file function mergeSuggestionWithVisContext({ suggestion, visAttributes, @@ -55,7 +55,6 @@ function mergeSuggestionWithVisContext({ !datasourceState?.layers || Object.values(datasourceState?.layers).some((layer) => layer.columns?.some( - // unknown column (c: { fieldName: string }) => !context?.textBasedColumns?.find((col) => col.name === c.fieldName) ) @@ -124,6 +123,7 @@ export const suggestionsApi = ({ dataViews, }); if (!suggestions.length) return []; + const activeVisualization = suggestions[0]; if ( activeVisualization.incomplete || @@ -158,25 +158,17 @@ export const suggestionsApi = ({ XYSuggestion.visualizationId ]?.switchVisualizationType?.(chartType, XYSuggestion?.visualizationState); - const updatedSuggestion = { - ...XYSuggestion, - visualizationState, - }; - return [ - preferredVisAttributes - ? mergeSuggestionWithVisContext({ - suggestion: updatedSuggestion, - visAttributes: preferredVisAttributes, - context, - }) - : updatedSuggestion, + { + ...XYSuggestion, + visualizationState, + }, ]; } // in case the user asks for another type (except from area, line) check if it exists // in suggestions and return this instead - if (suggestions.length > 1 && preferredChartType && !preferredVisAttributes) { - const suggestionFromModel = suggestions.find( + if (newSuggestions.length > 1 && preferredChartType && !preferredVisAttributes) { + const suggestionFromModel = newSuggestions.find( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); if (suggestionFromModel) { @@ -184,8 +176,8 @@ export const suggestionsApi = ({ } } - if (suggestions.length > 1 && preferredChartType && preferredVisAttributes) { - const suggestionFromModel = suggestions.find( + if (newSuggestions.length > 1 && preferredChartType && preferredVisAttributes) { + const suggestionFromModel = newSuggestions.find( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); if (suggestionFromModel) { From ae88fa75cb20442bdc5317ec68b0e79550da7990 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 24 Oct 2024 12:06:10 +0200 Subject: [PATCH 13/38] Cleanup --- .../unified_histogram/public/services/lens_vis_service.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index 7ff53ae911b4e..2b0353e43c818 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -518,9 +518,6 @@ export class LensVisService { if (breakdownColumn) { context.textBasedColumns.push(breakdownColumn); } - // const preferredChartType = preferredVisAttributes - // ? getPreferredChartType(preferredVisAttributes) - // : undefined; const preferredChartType = preferredVisAttributes?.visualizationType.replace( LENS_PREFIX, @@ -614,10 +611,6 @@ export class LensVisService { }): Suggestion[] => { const { dataView, columns, query, isPlainRecord } = queryParams; - // const preferredChartType = preferredVisAttributes - // ? getPreferredChartType(preferredVisAttributes) - // : undefined; - const preferredChartType = preferredVisAttributes?.visualizationType.replace( LENS_PREFIX, '' From d77b9da92254aae06fd61ed74d711dc5ba701599 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 25 Oct 2024 09:54:19 +0200 Subject: [PATCH 14/38] Simplification --- .../lens/public/lens_suggestions_api.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 003e2fae47f71..3de696a4f7a1a 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -167,29 +167,23 @@ export const suggestionsApi = ({ } // in case the user asks for another type (except from area, line) check if it exists // in suggestions and return this instead - if (newSuggestions.length > 1 && preferredChartType && !preferredVisAttributes) { + if (newSuggestions.length > 1 && preferredChartType) { const suggestionFromModel = newSuggestions.find( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); if (suggestionFromModel) { - return [suggestionFromModel]; - } - } + const suggestion = preferredVisAttributes + ? mergeSuggestionWithVisContext({ + suggestion: suggestionFromModel, + visAttributes: preferredVisAttributes, + context, + }) + : suggestionFromModel; - if (newSuggestions.length > 1 && preferredChartType && preferredVisAttributes) { - const suggestionFromModel = newSuggestions.find( - (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) - ); - if (suggestionFromModel) { - return [ - mergeSuggestionWithVisContext({ - suggestion: suggestionFromModel, - visAttributes: preferredVisAttributes, - context, - }), - ]; + return [suggestion]; } } + const suggestionsList = [activeVisualization, ...newSuggestions]; // if there is no preference from the user, send everything From 712e7890e5277086c615b2bd229fc4f149ac37ac Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 25 Oct 2024 12:32:30 +0200 Subject: [PATCH 15/38] Apply the change only if the vis state is different --- .../public/services/lens_vis_service.ts | 14 ++-- .../lens/public/lens_suggestions_api.ts | 69 ++++++++++++++++--- x-pack/plugins/lens/public/types.ts | 2 + .../datatable/visualization.tsx | 5 ++ .../visualizations/gauge/visualization.tsx | 6 ++ .../visualizations/heatmap/visualization.tsx | 6 ++ .../visualizations/metric/visualization.tsx | 6 ++ .../partition/visualization.tsx | 8 +++ .../tagcloud/tagcloud_visualization.tsx | 6 ++ .../visualizations/xy/visualization.tsx | 7 ++ 10 files changed, 110 insertions(+), 19 deletions(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index 2b0353e43c818..c1a88b655652d 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -519,10 +519,9 @@ export class LensVisService { context.textBasedColumns.push(breakdownColumn); } - const preferredChartType = preferredVisAttributes?.visualizationType.replace( - LENS_PREFIX, - '' - ) as ChartType; + const preferredChartType = preferredVisAttributes + ? (preferredVisAttributes?.visualizationType.replace(LENS_PREFIX, '') as ChartType) + : undefined; // here the attributes contain the main query and not the histogram one const updatedAttributesWithQuery = preferredVisAttributes @@ -611,10 +610,9 @@ export class LensVisService { }): Suggestion[] => { const { dataView, columns, query, isPlainRecord } = queryParams; - const preferredChartType = preferredVisAttributes?.visualizationType.replace( - LENS_PREFIX, - '' - ) as ChartType; + const preferredChartType = preferredVisAttributes + ? (preferredVisAttributes?.visualizationType.replace(LENS_PREFIX, '') as ChartType) + : undefined; const context = { dataViewSpec: dataView?.toSpec(), diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 3de696a4f7a1a..2511065f5dd8c 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -11,6 +11,7 @@ import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_h import type { DatasourceMap, VisualizationMap, VisualizeEditorContext, Suggestion } from './types'; import type { DataViewsState } from './state_management'; import type { TypedLensByValueInput } from './embeddable/embeddable_component'; +import type { XYState, PieVisualizationState } from '.'; interface SuggestionsApiProps { context: VisualizeFieldContext | VisualizeEditorContext; @@ -21,6 +22,37 @@ interface SuggestionsApiProps { preferredChartType?: ChartType; preferredVisAttributes?: TypedLensByValueInput['attributes']; } + +const findPreferredSuggestion = ({ + suggestionsList, + visAttributes, +}: { + suggestionsList: Suggestion[]; + visAttributes: TypedLensByValueInput['attributes']; +}): Suggestion | undefined => { + const preferredChartType = visAttributes?.visualizationType; + if (suggestionsList.length === 1) { + return suggestionsList[0]; + } + + if (preferredChartType === 'lnsXY') { + const seriesType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; + const suggestion = suggestionsList.find( + (s) => (s.visualizationState as XYState).preferredSeriesType === seriesType + ); + if (suggestion) return suggestion; + } + if (preferredChartType === 'lnsPie') { + const shape = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; + const suggestion = suggestionsList.find( + (s) => (s.visualizationState as PieVisualizationState).shape === shape + ); + if (suggestion) return suggestion; + } + + return undefined; +}; + // ToDo: Move to a new file function mergeSuggestionWithVisContext({ suggestion, @@ -168,19 +200,34 @@ export const suggestionsApi = ({ // in case the user asks for another type (except from area, line) check if it exists // in suggestions and return this instead if (newSuggestions.length > 1 && preferredChartType) { - const suggestionFromModel = newSuggestions.find( + const compatibleSuggestions = newSuggestions.filter( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); - if (suggestionFromModel) { - const suggestion = preferredVisAttributes - ? mergeSuggestionWithVisContext({ - suggestion: suggestionFromModel, - visAttributes: preferredVisAttributes, - context, - }) - : suggestionFromModel; - - return [suggestion]; + + if (compatibleSuggestions.length && !preferredVisAttributes) { + return compatibleSuggestions[0]; + } + if (compatibleSuggestions.length && preferredVisAttributes) { + const preferredSuggestion = findPreferredSuggestion({ + visAttributes: preferredVisAttributes, + suggestionsList: compatibleSuggestions, + }); + + const layersAreEqual = visualizationMap[ + preferredVisAttributes.visualizationType + ]?.areLayersEqual( + preferredSuggestion?.visualizationState, + preferredVisAttributes.state.visualization + ); + if (preferredSuggestion && !layersAreEqual) { + const suggestion = mergeSuggestionWithVisContext({ + suggestion: preferredSuggestion, + visAttributes: preferredVisAttributes, + context, + }); + + return [suggestion]; + } } } diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 5b5e33564cc7d..d430aebc984e9 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -1338,6 +1338,8 @@ export interface Visualization Suggestion | undefined; + areLayersEqual: (state1: P, state2: P) => boolean; + isEqual?: ( state1: P, references1: SavedObjectReference[], diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index 55dea2be2e370..f2efe92be268b 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { Ast } from '@kbn/interpreter'; import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; import { PaletteRegistry, CUSTOM_PALETTE, PaletteOutput, CustomPaletteParams } from '@kbn/coloring'; import { ThemeServiceStart } from '@kbn/core/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; @@ -712,6 +713,10 @@ export const getDatatableVisualization = ({ return state; } }, + areLayersEqual(state1, state2) { + state1 = { ...state1, layerId: state2.layerId }; + return isEqual(state1, state2); + }, getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index e1ef2b03e4264..af792de5b4837 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -25,6 +25,7 @@ import { } from '@kbn/expression-gauge-plugin/public'; import { IconChartGauge } from '@kbn/chart-icons'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import { isEqual } from 'lodash'; import type { FormBasedPersistedState } from '../../datasources/form_based/types'; import type { DatasourceLayers, @@ -559,6 +560,11 @@ export const getGaugeVisualization = ({ return warnings; }, + areLayersEqual(state1, state2) { + state1 = { ...state1, layerId: state2.layerId }; + return isEqual(state1, state2); + }, + getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index 1fb747427a05d..c927a8447f91e 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -22,6 +22,7 @@ import { HeatmapLegendExpressionFunctionDefinition, } from '@kbn/expression-heatmap-plugin/common'; import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/common'; +import { isEqual } from 'lodash'; import type { OperationMetadata, Suggestion, UserMessage, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; @@ -477,6 +478,11 @@ export const getHeatmapVisualization = ({ return [...errors, ...warnings]; }, + areLayersEqual(state1, state2) { + state1 = { ...state1, layerId: state2.layerId }; + return isEqual(state1, state2); + }, + getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index 6d3bd42f26cfa..64332580d60b3 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -13,6 +13,7 @@ import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { euiLightVars, euiThemeVars } from '@kbn/ui-theme'; import { IconChartMetric } from '@kbn/chart-icons'; import { AccessorConfig } from '@kbn/visualization-ui-components'; +import { isEqual } from 'lodash'; import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { layerTypes } from '../../../common/layer_types'; import type { FormBasedPersistedState } from '../../datasources/form_based/types'; @@ -585,6 +586,11 @@ export const getMetricVisualization = ({ }; }, + areLayersEqual(state1, state2) { + state1 = { ...state1, layerId: state2.layerId }; + return isEqual(state1, state2); + }, + getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index 661921caaa1ef..2f128a73988e8 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -21,6 +21,7 @@ import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/con import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { AccessorConfig } from '@kbn/visualization-ui-components'; import useObservable from 'react-use/lib/useObservable'; +import { isEqual } from 'lodash'; import type { FormBasedPersistedState } from '../../datasources/form_based/types'; import type { Visualization, @@ -534,6 +535,13 @@ export const getPieVisualization = ({ return ; }, + areLayersEqual(state1, state2) { + state1.layers = state1.layers.map((layer, index) => { + return { ...layer, layerId: state2.layers[index].layerId }; + }); + return isEqual(state1.layers, state2.layers); + }, + getSuggestionFromConvertToLensContext(props) { const context = props.context; if (!isPartitionVisConfiguration(context)) { diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx index 8d962ef076093..413509e197053 100644 --- a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx @@ -16,6 +16,7 @@ import { buildExpressionFunction, ExpressionFunctionTheme, } from '@kbn/expressions-plugin/common'; +import { isEqual } from 'lodash'; import { PaletteRegistry, getColorsFromMapping } from '@kbn/coloring'; import { IconChartTagcloud } from '@kbn/chart-icons'; import { SystemPaletteExpressionFunctionDefinition } from '@kbn/charts-plugin/common'; @@ -71,6 +72,11 @@ export const getTagcloudVisualization = ({ return [state.layerId]; }, + areLayersEqual(state1, state2) { + state1 = { ...state1, layerId: state2.layerId }; + return isEqual(state1, state2); + }, + getDescription() { return { icon: IconChartTagcloud, diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 51f79bf58eeac..542d31ca2da8c 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -1067,6 +1067,13 @@ export const getXyVisualization = ({ return null; }, + areLayersEqual(state1, state2) { + state1.layers = state1.layers.map((layer, index) => { + return { ...layer, layerId: state2.layers[index].layerId }; + }); + return isEqual(state1.layers, state2.layers); + }, + getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array>; const suggestion: Suggestion = { From 312164fae348198904323ab889605f1ceaf6f136 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 25 Oct 2024 13:36:01 +0200 Subject: [PATCH 16/38] Fixes --- .../lens/public/lens_suggestions_api.ts | 129 ++++++++++-------- x-pack/plugins/lens/public/types.ts | 2 - .../datatable/visualization.tsx | 5 - .../visualizations/gauge/visualization.tsx | 6 - .../visualizations/heatmap/visualization.tsx | 6 - .../visualizations/metric/visualization.tsx | 6 - .../partition/visualization.tsx | 8 -- .../tagcloud/tagcloud_visualization.tsx | 6 - .../visualizations/xy/visualization.tsx | 7 - 9 files changed, 72 insertions(+), 103 deletions(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 2511065f5dd8c..9445a0c1b8961 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -11,7 +11,7 @@ import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_h import type { DatasourceMap, VisualizationMap, VisualizeEditorContext, Suggestion } from './types'; import type { DataViewsState } from './state_management'; import type { TypedLensByValueInput } from './embeddable/embeddable_component'; -import type { XYState, PieVisualizationState } from '.'; +// import type { XYState, PieVisualizationState } from '.'; interface SuggestionsApiProps { context: VisualizeFieldContext | VisualizeEditorContext; @@ -23,35 +23,35 @@ interface SuggestionsApiProps { preferredVisAttributes?: TypedLensByValueInput['attributes']; } -const findPreferredSuggestion = ({ - suggestionsList, - visAttributes, -}: { - suggestionsList: Suggestion[]; - visAttributes: TypedLensByValueInput['attributes']; -}): Suggestion | undefined => { - const preferredChartType = visAttributes?.visualizationType; - if (suggestionsList.length === 1) { - return suggestionsList[0]; - } +// const findPreferredSuggestion = ({ +// suggestionsList, +// visAttributes, +// }: { +// suggestionsList: Suggestion[]; +// visAttributes: TypedLensByValueInput['attributes']; +// }): Suggestion | undefined => { +// const preferredChartType = visAttributes?.visualizationType; +// if (suggestionsList.length === 1) { +// return suggestionsList[0]; +// } - if (preferredChartType === 'lnsXY') { - const seriesType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; - const suggestion = suggestionsList.find( - (s) => (s.visualizationState as XYState).preferredSeriesType === seriesType - ); - if (suggestion) return suggestion; - } - if (preferredChartType === 'lnsPie') { - const shape = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; - const suggestion = suggestionsList.find( - (s) => (s.visualizationState as PieVisualizationState).shape === shape - ); - if (suggestion) return suggestion; - } +// if (preferredChartType === 'lnsXY') { +// const seriesType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; +// const suggestion = suggestionsList.find( +// (s) => (s.visualizationState as XYState).preferredSeriesType === seriesType +// ); +// if (suggestion) return suggestion; +// } +// if (preferredChartType === 'lnsPie') { +// const shape = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; +// const suggestion = suggestionsList.find( +// (s) => (s.visualizationState as PieVisualizationState).shape === shape +// ); +// if (suggestion) return suggestion; +// } - return undefined; -}; +// return undefined; +// }; // ToDo: Move to a new file function mergeSuggestionWithVisContext({ @@ -94,14 +94,16 @@ function mergeSuggestionWithVisContext({ ) { return suggestion; } - + const layerIds = Object.keys(datasourceState.layers); try { return { - ...suggestion, - datasourceState, + title: visAttributes.title, + visualizationId: visAttributes.visualizationType, visualizationState: visAttributes.state.visualization, + keptLayerIds: layerIds, + datasourceState, datasourceId, - }; + } as Suggestion; } catch { return suggestion; } @@ -199,36 +201,49 @@ export const suggestionsApi = ({ } // in case the user asks for another type (except from area, line) check if it exists // in suggestions and return this instead + if (newSuggestions.length > 1 && preferredChartType) { - const compatibleSuggestions = newSuggestions.filter( + const compatibleSuggestion = newSuggestions.find( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); - if (compatibleSuggestions.length && !preferredVisAttributes) { - return compatibleSuggestions[0]; - } - if (compatibleSuggestions.length && preferredVisAttributes) { - const preferredSuggestion = findPreferredSuggestion({ - visAttributes: preferredVisAttributes, - suggestionsList: compatibleSuggestions, - }); - - const layersAreEqual = visualizationMap[ - preferredVisAttributes.visualizationType - ]?.areLayersEqual( - preferredSuggestion?.visualizationState, - preferredVisAttributes.state.visualization - ); - if (preferredSuggestion && !layersAreEqual) { - const suggestion = mergeSuggestionWithVisContext({ - suggestion: preferredSuggestion, - visAttributes: preferredVisAttributes, - context, - }); - - return [suggestion]; - } + if (compatibleSuggestion) { + const suggestion = preferredVisAttributes + ? mergeSuggestionWithVisContext({ + suggestion: compatibleSuggestion, + visAttributes: preferredVisAttributes, + context, + }) + : compatibleSuggestion; + + return [suggestion]; } + + // if (compatibleSuggestions.length && !preferredVisAttributes) { + // return compatibleSuggestions[0]; + // } + // if (compatibleSuggestions.length && preferredVisAttributes) { + // const preferredSuggestion = findPreferredSuggestion({ + // visAttributes: preferredVisAttributes, + // suggestionsList: compatibleSuggestions, + // }); + + // const layersAreEqual = visualizationMap[ + // preferredVisAttributes.visualizationType + // ]?.areLayersEqual( + // preferredSuggestion?.visualizationState, + // preferredVisAttributes.state.visualization + // ); + // if (preferredSuggestion) { + // const suggestion = mergeSuggestionWithVisContext({ + // suggestion: preferredSuggestion, + // visAttributes: preferredVisAttributes, + // context, + // }); + + // return [suggestion]; + // } + // } } const suggestionsList = [activeVisualization, ...newSuggestions]; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index d430aebc984e9..5b5e33564cc7d 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -1338,8 +1338,6 @@ export interface Visualization Suggestion | undefined; - areLayersEqual: (state1: P, state2: P) => boolean; - isEqual?: ( state1: P, references1: SavedObjectReference[], diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index f2efe92be268b..55dea2be2e370 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { Ast } from '@kbn/interpreter'; import { i18n } from '@kbn/i18n'; -import { isEqual } from 'lodash'; import { PaletteRegistry, CUSTOM_PALETTE, PaletteOutput, CustomPaletteParams } from '@kbn/coloring'; import { ThemeServiceStart } from '@kbn/core/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; @@ -713,10 +712,6 @@ export const getDatatableVisualization = ({ return state; } }, - areLayersEqual(state1, state2) { - state1 = { ...state1, layerId: state2.layerId }; - return isEqual(state1, state2); - }, getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index af792de5b4837..e1ef2b03e4264 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -25,7 +25,6 @@ import { } from '@kbn/expression-gauge-plugin/public'; import { IconChartGauge } from '@kbn/chart-icons'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; -import { isEqual } from 'lodash'; import type { FormBasedPersistedState } from '../../datasources/form_based/types'; import type { DatasourceLayers, @@ -560,11 +559,6 @@ export const getGaugeVisualization = ({ return warnings; }, - areLayersEqual(state1, state2) { - state1 = { ...state1, layerId: state2.layerId }; - return isEqual(state1, state2); - }, - getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index c927a8447f91e..1fb747427a05d 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -22,7 +22,6 @@ import { HeatmapLegendExpressionFunctionDefinition, } from '@kbn/expression-heatmap-plugin/common'; import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/common'; -import { isEqual } from 'lodash'; import type { OperationMetadata, Suggestion, UserMessage, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; @@ -478,11 +477,6 @@ export const getHeatmapVisualization = ({ return [...errors, ...warnings]; }, - areLayersEqual(state1, state2) { - state1 = { ...state1, layerId: state2.layerId }; - return isEqual(state1, state2); - }, - getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index 64332580d60b3..6d3bd42f26cfa 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -13,7 +13,6 @@ import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { euiLightVars, euiThemeVars } from '@kbn/ui-theme'; import { IconChartMetric } from '@kbn/chart-icons'; import { AccessorConfig } from '@kbn/visualization-ui-components'; -import { isEqual } from 'lodash'; import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { layerTypes } from '../../../common/layer_types'; import type { FormBasedPersistedState } from '../../datasources/form_based/types'; @@ -586,11 +585,6 @@ export const getMetricVisualization = ({ }; }, - areLayersEqual(state1, state2) { - state1 = { ...state1, layerId: state2.layerId }; - return isEqual(state1, state2); - }, - getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array< Suggestion diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index 2f128a73988e8..661921caaa1ef 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -21,7 +21,6 @@ import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/con import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { AccessorConfig } from '@kbn/visualization-ui-components'; import useObservable from 'react-use/lib/useObservable'; -import { isEqual } from 'lodash'; import type { FormBasedPersistedState } from '../../datasources/form_based/types'; import type { Visualization, @@ -535,13 +534,6 @@ export const getPieVisualization = ({ return ; }, - areLayersEqual(state1, state2) { - state1.layers = state1.layers.map((layer, index) => { - return { ...layer, layerId: state2.layers[index].layerId }; - }); - return isEqual(state1.layers, state2.layers); - }, - getSuggestionFromConvertToLensContext(props) { const context = props.context; if (!isPartitionVisConfiguration(context)) { diff --git a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx index 413509e197053..8d962ef076093 100644 --- a/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/tagcloud/tagcloud_visualization.tsx @@ -16,7 +16,6 @@ import { buildExpressionFunction, ExpressionFunctionTheme, } from '@kbn/expressions-plugin/common'; -import { isEqual } from 'lodash'; import { PaletteRegistry, getColorsFromMapping } from '@kbn/coloring'; import { IconChartTagcloud } from '@kbn/chart-icons'; import { SystemPaletteExpressionFunctionDefinition } from '@kbn/charts-plugin/common'; @@ -72,11 +71,6 @@ export const getTagcloudVisualization = ({ return [state.layerId]; }, - areLayersEqual(state1, state2) { - state1 = { ...state1, layerId: state2.layerId }; - return isEqual(state1, state2); - }, - getDescription() { return { icon: IconChartTagcloud, diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 542d31ca2da8c..51f79bf58eeac 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -1067,13 +1067,6 @@ export const getXyVisualization = ({ return null; }, - areLayersEqual(state1, state2) { - state1.layers = state1.layers.map((layer, index) => { - return { ...layer, layerId: state2.layers[index].layerId }; - }); - return isEqual(state1.layers, state2.layers); - }, - getSuggestionFromConvertToLensContext({ suggestions, context }) { const allSuggestions = suggestions as Array>; const suggestion: Suggestion = { From 6793d302db97b5abc92006f8ef58c369760e9c2f Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 25 Oct 2024 13:48:35 +0200 Subject: [PATCH 17/38] fixes --- .../public/services/lens_vis_service.ts | 33 +------------------ .../lens/public/lens_suggestions_api.ts | 8 ++--- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index c1a88b655652d..3fa207af4c023 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -283,33 +283,6 @@ export class LensVisService { } } - if (externalVisContext && queryParams.isPlainRecord) { - // externalVisContext can be based on an unfamiliar suggestion (not a part of allSuggestions), but it was saved before, so we try to restore it too - const derivedSuggestion = deriveLensSuggestionFromLensAttributes({ - externalVisContext, - queryParams, - }); - - if (derivedSuggestion) { - availableSuggestionsWithType.push({ - suggestion: derivedSuggestion, - type: UnifiedHistogramSuggestionType.lensSuggestion, - }); - } - } - - if (externalVisContext) { - // try to find a suggestion that is compatible with the external vis context - const matchingItem = availableSuggestionsWithType.find((item) => - isSuggestionShapeAndVisContextCompatible(item.suggestion, externalVisContext) - ); - - if (matchingItem) { - currentSuggestion = matchingItem.suggestion; - type = matchingItem.type; - } - } - if (!currentSuggestion && availableSuggestionsWithType.length) { // otherwise pick any first available suggestion currentSuggestion = availableSuggestionsWithType[0].suggestion; @@ -519,10 +492,6 @@ export class LensVisService { context.textBasedColumns.push(breakdownColumn); } - const preferredChartType = preferredVisAttributes - ? (preferredVisAttributes?.visualizationType.replace(LENS_PREFIX, '') as ChartType) - : undefined; - // here the attributes contain the main query and not the histogram one const updatedAttributesWithQuery = preferredVisAttributes ? assingQueryToLensLayers(preferredVisAttributes, { @@ -535,7 +504,7 @@ export class LensVisService { context, dataView, ['lnsDatatable'], - preferredChartType, + 'lnsXY' as ChartType, updatedAttributesWithQuery ) ?? []; if (suggestions.length) { diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index 9445a0c1b8961..ef3074b1301c6 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -201,9 +201,9 @@ export const suggestionsApi = ({ } // in case the user asks for another type (except from area, line) check if it exists // in suggestions and return this instead - - if (newSuggestions.length > 1 && preferredChartType) { - const compatibleSuggestion = newSuggestions.find( + const suggestionsList = [activeVisualization, ...newSuggestions]; + if (suggestionsList.length > 1 && preferredChartType) { + const compatibleSuggestion = suggestionsList.find( (s) => s.title.includes(preferredChartType) || s.visualizationId.includes(preferredChartType) ); @@ -246,8 +246,6 @@ export const suggestionsApi = ({ // } } - const suggestionsList = [activeVisualization, ...newSuggestions]; - // if there is no preference from the user, send everything // until we separate the text based suggestions logic from the dataview one, // we want to sort XY first From 424ba416b875ce9adaa2fe38e650c4ef2d0ed66f Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 25 Oct 2024 16:00:32 +0200 Subject: [PATCH 18/38] Fix --- .../lens/public/lens_suggestions_api.ts | 68 ++----------------- 1 file changed, 6 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api.ts index ef3074b1301c6..dff12d75a2221 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api.ts @@ -11,7 +11,6 @@ import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_h import type { DatasourceMap, VisualizationMap, VisualizeEditorContext, Suggestion } from './types'; import type { DataViewsState } from './state_management'; import type { TypedLensByValueInput } from './embeddable/embeddable_component'; -// import type { XYState, PieVisualizationState } from '.'; interface SuggestionsApiProps { context: VisualizeFieldContext | VisualizeEditorContext; @@ -23,36 +22,6 @@ interface SuggestionsApiProps { preferredVisAttributes?: TypedLensByValueInput['attributes']; } -// const findPreferredSuggestion = ({ -// suggestionsList, -// visAttributes, -// }: { -// suggestionsList: Suggestion[]; -// visAttributes: TypedLensByValueInput['attributes']; -// }): Suggestion | undefined => { -// const preferredChartType = visAttributes?.visualizationType; -// if (suggestionsList.length === 1) { -// return suggestionsList[0]; -// } - -// if (preferredChartType === 'lnsXY') { -// const seriesType = (visAttributes?.state?.visualization as XYState)?.preferredSeriesType; -// const suggestion = suggestionsList.find( -// (s) => (s.visualizationState as XYState).preferredSeriesType === seriesType -// ); -// if (suggestion) return suggestion; -// } -// if (preferredChartType === 'lnsPie') { -// const shape = (visAttributes?.state?.visualization as PieVisualizationState)?.shape; -// const suggestion = suggestionsList.find( -// (s) => (s.visualizationState as PieVisualizationState).shape === shape -// ); -// if (suggestion) return suggestion; -// } - -// return undefined; -// }; - // ToDo: Move to a new file function mergeSuggestionWithVisContext({ suggestion, @@ -85,11 +54,12 @@ function mergeSuggestionWithVisContext({ // should be based on same columns if ( !datasourceState?.layers || - Object.values(datasourceState?.layers).some((layer) => - layer.columns?.some( - (c: { fieldName: string }) => - !context?.textBasedColumns?.find((col) => col.name === c.fieldName) - ) + Object.values(datasourceState?.layers).some( + (layer) => + layer.columns?.some( + (c: { fieldName: string }) => + !context?.textBasedColumns?.find((col) => col.name === c.fieldName) + ) || layer.columns?.length !== context?.textBasedColumns?.length ) ) { return suggestion; @@ -218,32 +188,6 @@ export const suggestionsApi = ({ return [suggestion]; } - - // if (compatibleSuggestions.length && !preferredVisAttributes) { - // return compatibleSuggestions[0]; - // } - // if (compatibleSuggestions.length && preferredVisAttributes) { - // const preferredSuggestion = findPreferredSuggestion({ - // visAttributes: preferredVisAttributes, - // suggestionsList: compatibleSuggestions, - // }); - - // const layersAreEqual = visualizationMap[ - // preferredVisAttributes.visualizationType - // ]?.areLayersEqual( - // preferredSuggestion?.visualizationState, - // preferredVisAttributes.state.visualization - // ); - // if (preferredSuggestion) { - // const suggestion = mergeSuggestionWithVisContext({ - // suggestion: preferredSuggestion, - // visAttributes: preferredVisAttributes, - // context, - // }); - - // return [suggestion]; - // } - // } } // if there is no preference from the user, send everything From 9782de50722727a011f9950a3943f50584eb7125 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 11:01:49 +0100 Subject: [PATCH 19/38] Adds unit tests --- .../public/services/lens_vis_service.ts | 4 +- .../public/utils/external_vis_context.test.ts | 60 +++++ .../public/utils/external_vis_context.ts | 2 +- .../public/lens_suggestions_api/Readme.md | 77 +++++++ .../lens_suggestions_api/helpers.test.ts | 206 ++++++++++++++++++ .../public/lens_suggestions_api/helpers.ts | 74 +++++++ .../index.ts} | 66 +----- .../lens_suggestions_api.test.ts | 113 +++++++++- 8 files changed, 531 insertions(+), 71 deletions(-) create mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/Readme.md create mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts create mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts rename x-pack/plugins/lens/public/{lens_suggestions_api.ts => lens_suggestions_api/index.ts} (70%) rename x-pack/plugins/lens/public/{ => lens_suggestions_api}/lens_suggestions_api.test.ts (76%) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index 3fa207af4c023..0c584eb1a83bd 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -42,7 +42,7 @@ import { isSuggestionShapeAndVisContextCompatible, deriveLensSuggestionFromLensAttributes, type QueryParams, - assingQueryToLensLayers, + injectESQLQueryIntoLensLayers, } from '../utils/external_vis_context'; import { computeInterval } from '../utils/compute_interval'; import { fieldSupportsBreakdown } from '../utils/field_supports_breakdown'; @@ -494,7 +494,7 @@ export class LensVisService { // here the attributes contain the main query and not the histogram one const updatedAttributesWithQuery = preferredVisAttributes - ? assingQueryToLensLayers(preferredVisAttributes, { + ? injectESQLQueryIntoLensLayers(preferredVisAttributes, { esql: esqlQuery, }) : undefined; diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.test.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.test.ts index 2931d3a8410ca..1cbad8b308078 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.test.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.test.ts @@ -13,6 +13,7 @@ import { canImportVisContext, exportVisContext, isSuggestionShapeAndVisContextCompatible, + injectESQLQueryIntoLensLayers, } from './external_vis_context'; import { getLensVisMock } from '../__mocks__/lens_vis'; import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; @@ -162,4 +163,63 @@ describe('external_vis_context', () => { ).toBe(true); }); }); + + describe('injectESQLQueryIntoLensLayers', () => { + it('should return the Lens attributes as they are for unknown datasourceId', async () => { + const attributes = { + visualizationType: 'lnsXY', + state: { + visualization: { preferredSeriesType: 'line' }, + datasourceStates: { unknownId: { layers: {} } }, + }, + } as unknown as UnifiedHistogramVisContext['attributes']; + expect(injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo' })).toStrictEqual( + attributes + ); + }); + + it('should return the Lens attributes as they are for DSL config (formbased)', async () => { + const attributes = { + visualizationType: 'lnsXY', + state: { + visualization: { preferredSeriesType: 'line' }, + datasourceStates: { formBased: { layers: {} } }, + }, + } as UnifiedHistogramVisContext['attributes']; + expect(injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo' })).toStrictEqual( + attributes + ); + }); + + it('should inject the query to the Lens attributes for ES|QL config (textbased)', async () => { + const attributes = { + visualizationType: 'lnsXY', + state: { + visualization: { preferredSeriesType: 'line' }, + datasourceStates: { textBased: { layers: { layer1: { query: { esql: 'from foo' } } } } }, + }, + } as unknown as UnifiedHistogramVisContext['attributes']; + + const expectedAttributes = { + ...attributes, + state: { + ...attributes.state, + datasourceStates: { + ...attributes.state.datasourceStates, + textBased: { + ...attributes.state.datasourceStates.textBased, + layers: { + layer1: { + query: { esql: 'from foo | stats count(*)' }, + }, + }, + }, + }, + }, + } as unknown as UnifiedHistogramVisContext['attributes']; + expect( + injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo | stats count(*)' }) + ).toStrictEqual(expectedAttributes); + }); + }); }); diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.ts index 79b74d263b4ed..4c1555bfbcaba 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.ts @@ -103,7 +103,7 @@ export const isSuggestionShapeAndVisContextCompatible = ( ); }; -export const assingQueryToLensLayers = ( +export const injectESQLQueryIntoLensLayers = ( visAttributes: UnifiedHistogramVisContext['attributes'], query: AggregateQuery ) => { diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md b/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md new file mode 100644 index 0000000000000..abae6d508f3ae --- /dev/null +++ b/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md @@ -0,0 +1,77 @@ +# Lens Suggestions API + +This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards. + +## Overview + +The Lens Suggestions API is designed to provide suggestions for visualizations based on a given ES|QL query. It helps users to quickly find the most relevant visualizations for their data. + +## Getting Started + +To use the Lens Suggestions API, you need to import it from the Lens plugin: + +```typescript +import useAsync from 'react-use/lib/useAsync'; + +const lensHelpersAsync = useAsync(() => { + return lensService?.stateHelperApi() ?? Promise.resolve(null); + }, [lensService]); + + if (lensHelpersAsync.value) { + const suggestionsApi = lensHelpersAsync.value.suggestions; + } +``` + +## The api + +The api returns an array of suggestions. + +#### Parameters + + dataView: DataView; + visualizationMap?: VisualizationMap; + datasourceMap?: DatasourceMap; + excludedVisualizations?: string[]; + preferredChartType?: ChartType; + preferredVisAttributes?: TypedLensByValueInput['attributes']; + +- `context`: The context as descibed by the VisualizeFieldContext. +- `dataView`: The dataView, can be an adhoc one too. For ES|QL you can create a dataview like this + +```typescript +const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; +const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); +``` +Optional parameters: +- `preferredChartType`: Use this if you want the suggestions api to prioritize a specific suggestion type. +- `preferredVisAttributes`: Use this with the preferredChartType if you want to prioritize a specific suggestion type with a non-default visualization state. + +#### Returns + +An array of suggestion objects + +## Example Usage + +```typescript +const abc = new AbortController(); + +const columns = await getESQLQueryColumns({ + esqlQuery, + search: dataService.search.search, + signal: abc.signal, + timeRange: dataService.query.timefilter.timefilter.getAbsoluteTime(), +}); + +const context = { + dataViewSpec: dataView?.toSpec(false), + fieldName: '', + textBasedColumns: columns, + query: { esql: esqlQuery }, +}; + +const chartSuggestions = lensHelpersAsync.value.suggestions(context, dataView); + +suggestions.forEach(suggestion => { + console.log(`Suggestion: ${suggestion.title}, Score: ${suggestion.score}`); +}); +``` \ No newline at end of file diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts new file mode 100644 index 0000000000000..177a7e2e0d33c --- /dev/null +++ b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { DatatableColumn } from '@kbn/expressions-plugin/common'; +import { mergeSuggestionWithVisContext } from './helpers'; +import { mockAllSuggestions } from '../mocks'; +import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; + +const context = { + dataViewSpec: { + id: 'index1', + title: 'index1', + name: 'DataView', + }, + fieldName: '', + textBasedColumns: [ + { + id: 'field1', + name: 'field1', + meta: { + type: 'number', + }, + }, + { + id: 'field2', + name: 'field2', + meta: { + type: 'string', + }, + }, + ] as DatatableColumn[], + query: { + esql: 'FROM index1 | keep field1, field2', + }, +}; + +describe('lens suggestions api helpers', () => { + describe('mergeSuggestionWithVisContext', () => { + it('should return the suggestion as it is if the visualization types do not match', async () => { + const suggestion = mockAllSuggestions[0]; + const visAttributes = { + visualizationType: 'lnsXY', + state: { + visualization: { + preferredSeriesType: 'bar_stacked', + }, + datasourceStates: { textBased: { layers: {} } }, + }, + } as unknown as TypedLensByValueInput['attributes']; + expect(mergeSuggestionWithVisContext({ suggestion, visAttributes, context })).toStrictEqual( + suggestion + ); + }); + + it('should return the suggestion as it is if the context is not from ES|QL', async () => { + const nonESQLContext = { + dataViewSpec: { + id: 'index1', + title: 'index1', + name: 'DataView', + }, + fieldName: 'field1', + }; + const suggestion = mockAllSuggestions[0]; + const visAttributes = { + visualizationType: 'lnsHeatmap', + state: { + visualization: { + preferredSeriesType: 'bar_stacked', + }, + datasourceStates: { textBased: { layers: {} } }, + }, + } as unknown as TypedLensByValueInput['attributes']; + expect( + mergeSuggestionWithVisContext({ suggestion, visAttributes, context: nonESQLContext }) + ).toStrictEqual(suggestion); + }); + + it('should return the suggestion as it is for DSL config (formbased)', async () => { + const suggestion = mockAllSuggestions[0]; + const visAttributes = { + visualizationType: 'lnsHeatmap', + state: { + visualization: { + preferredSeriesType: 'bar_stacked', + }, + datasourceStates: { formBased: { layers: {} } }, + }, + } as unknown as TypedLensByValueInput['attributes']; + expect(mergeSuggestionWithVisContext({ suggestion, visAttributes, context })).toStrictEqual( + suggestion + ); + }); + + it('should return the suggestion as it is for columns that dont match the context', async () => { + const suggestion = mockAllSuggestions[0]; + const visAttributes = { + visualizationType: 'lnsHeatmap', + state: { + visualization: { + shape: 'heatmap', + }, + datasourceStates: { + textBased: { + layers: { + layer1: { + index: 'layer1', + query: { + esql: 'FROM kibana_sample_data_flights | keep Dest, AvgTicketPrice', + }, + columns: [ + { + columnId: 'colA', + fieldName: 'Dest', + meta: { + type: 'string', + }, + }, + { + columnId: 'colB', + fieldName: 'AvgTicketPrice', + meta: { + type: 'number', + }, + }, + ], + timeField: 'timestamp', + }, + }, + }, + }, + }, + } as unknown as TypedLensByValueInput['attributes']; + expect(mergeSuggestionWithVisContext({ suggestion, visAttributes, context })).toStrictEqual( + suggestion + ); + }); + + it('should return the suggestion updated with the attributes if the visualization types and the context columns match', async () => { + const suggestion = mockAllSuggestions[0]; + const visAttributes = { + visualizationType: 'lnsHeatmap', + state: { + visualization: { + shape: 'heatmap', + layerId: 'layer1', + layerType: 'data', + legend: { + isVisible: false, + position: 'left', + type: 'heatmap_legend', + }, + gridConfig: { + type: 'heatmap_grid', + isCellLabelVisible: true, + isYAxisLabelVisible: false, + isXAxisLabelVisible: false, + isYAxisTitleVisible: false, + isXAxisTitleVisible: false, + }, + valueAccessor: 'acc1', + xAccessor: 'acc2', + }, + datasourceStates: { + textBased: { + layers: { + layer1: { + index: 'layer1', + query: { + esql: 'FROM index1 | keep field1, field2', + }, + columns: [ + { + columnId: 'field2', + fieldName: 'field2', + meta: { + type: 'string', + }, + }, + { + columnId: 'field1', + fieldName: 'field1', + meta: { + type: 'number', + }, + }, + ], + timeField: 'timestamp', + }, + }, + }, + }, + }, + } as unknown as TypedLensByValueInput['attributes']; + const updatedSuggestion = mergeSuggestionWithVisContext({ + suggestion, + visAttributes, + context, + }); + expect(updatedSuggestion.visualizationState).toStrictEqual(visAttributes.state.visualization); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts new file mode 100644 index 0000000000000..14a7a5f5d06ba --- /dev/null +++ b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; +import type { VisualizeEditorContext, Suggestion } from '../types'; +import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; + +/** + * Returns the suggestion updated with external visualization state for ES|QL charts + * The visualization state is merged with the suggestion if the datasource is textBased, the columns match the context and the visualization type matches + * @param suggestion the suggestion to be updated + * @param visAttributes the preferred visualization attributes + * @param context the lens suggestions api context as being set by the consumers + * @returns updated suggestion + */ + +export function mergeSuggestionWithVisContext({ + suggestion, + visAttributes, + context, +}: { + suggestion: Suggestion; + visAttributes: TypedLensByValueInput['attributes']; + context: VisualizeFieldContext | VisualizeEditorContext; +}): Suggestion { + if ( + visAttributes.visualizationType !== suggestion.visualizationId || + !('textBasedColumns' in context) + ) { + return suggestion; + } + + // it should be one of 'formBased'/'textBased' and have value + const datasourceId: 'formBased' | 'textBased' | undefined = [ + 'formBased' as const, + 'textBased' as const, + ].find((key) => Boolean(visAttributes.state.datasourceStates[key])); + + // if the datasource is formBased, we should not merge + if (!datasourceId || datasourceId === 'formBased') { + return suggestion; + } + const datasourceState = Object.assign({}, visAttributes.state.datasourceStates[datasourceId]); + + // should be based on same columns + if ( + !datasourceState?.layers || + Object.values(datasourceState?.layers).some( + (layer) => + layer.columns?.some( + (c: { fieldName: string }) => + !context?.textBasedColumns?.find((col) => col.name === c.fieldName) + ) || layer.columns?.length !== context?.textBasedColumns?.length + ) + ) { + return suggestion; + } + const layerIds = Object.keys(datasourceState.layers); + try { + return { + title: visAttributes.title, + visualizationId: visAttributes.visualizationType, + visualizationState: visAttributes.state.visualization, + keptLayerIds: layerIds, + datasourceState, + datasourceId, + } as Suggestion; + } catch { + return suggestion; + } +} diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.ts b/x-pack/plugins/lens/public/lens_suggestions_api/index.ts similarity index 70% rename from x-pack/plugins/lens/public/lens_suggestions_api.ts rename to x-pack/plugins/lens/public/lens_suggestions_api/index.ts index dff12d75a2221..c73379d9a42cd 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/index.ts @@ -7,10 +7,11 @@ import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ChartType } from '@kbn/visualization-utils'; -import { getSuggestions } from './editor_frame_service/editor_frame/suggestion_helpers'; -import type { DatasourceMap, VisualizationMap, VisualizeEditorContext, Suggestion } from './types'; -import type { DataViewsState } from './state_management'; -import type { TypedLensByValueInput } from './embeddable/embeddable_component'; +import { getSuggestions } from '../editor_frame_service/editor_frame/suggestion_helpers'; +import type { DatasourceMap, VisualizationMap, VisualizeEditorContext } from '../types'; +import type { DataViewsState } from '../state_management'; +import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; +import { mergeSuggestionWithVisContext } from './helpers'; interface SuggestionsApiProps { context: VisualizeFieldContext | VisualizeEditorContext; @@ -22,63 +23,6 @@ interface SuggestionsApiProps { preferredVisAttributes?: TypedLensByValueInput['attributes']; } -// ToDo: Move to a new file -function mergeSuggestionWithVisContext({ - suggestion, - visAttributes, - context, -}: { - suggestion: Suggestion; - visAttributes: TypedLensByValueInput['attributes']; - context: VisualizeFieldContext | VisualizeEditorContext; -}): Suggestion { - if ( - visAttributes.visualizationType !== suggestion.visualizationId || - !('textBasedColumns' in context) - ) { - return suggestion; - } - - // it should be one of 'formBased'/'textBased' and have value - const datasourceId: 'formBased' | 'textBased' | undefined = [ - 'formBased' as const, - 'textBased' as const, - ].find((key) => Boolean(visAttributes.state.datasourceStates[key])); - - // if the datasource is formBased, we should not merge - if (!datasourceId || datasourceId === 'formBased') { - return suggestion; - } - const datasourceState = Object.assign({}, visAttributes.state.datasourceStates[datasourceId]); - - // should be based on same columns - if ( - !datasourceState?.layers || - Object.values(datasourceState?.layers).some( - (layer) => - layer.columns?.some( - (c: { fieldName: string }) => - !context?.textBasedColumns?.find((col) => col.name === c.fieldName) - ) || layer.columns?.length !== context?.textBasedColumns?.length - ) - ) { - return suggestion; - } - const layerIds = Object.keys(datasourceState.layers); - try { - return { - title: visAttributes.title, - visualizationId: visAttributes.visualizationType, - visualizationState: visAttributes.state.visualization, - keptLayerIds: layerIds, - datasourceState, - datasourceId, - } as Suggestion; - } catch { - return suggestion; - } -} - export const suggestionsApi = ({ context, dataView, diff --git a/x-pack/plugins/lens/public/lens_suggestions_api.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts similarity index 76% rename from x-pack/plugins/lens/public/lens_suggestions_api.test.ts rename to x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts index b4461d6dfb500..95e0911460eba 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api.test.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts @@ -7,9 +7,10 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { ChartType } from '@kbn/visualization-utils'; -import { createMockVisualization, DatasourceMock, createMockDatasource } from './mocks'; -import { DatasourceSuggestion } from './types'; -import { suggestionsApi } from './lens_suggestions_api'; +import { createMockVisualization, DatasourceMock, createMockDatasource } from '../mocks'; +import { DatasourceSuggestion } from '../types'; +import { suggestionsApi } from '.'; +import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSuggestion => ({ state, @@ -288,8 +289,7 @@ describe('suggestionsApi', () => { preferredChartType: ChartType.Line, }); expect(suggestions?.length).toEqual(1); - expect(suggestions?.[0]).toMatchInlineSnapshot( - ` + expect(suggestions?.[0]).toMatchInlineSnapshot(` Object { "changeType": "unchanged", "columns": 0, @@ -306,8 +306,107 @@ describe('suggestionsApi', () => { "preferredSeriesType": "line", }, } - ` - ); + `); + }); + + test('returns the suggestion with the preferred attributes ', async () => { + const dataView = { id: 'index1' } as unknown as DataView; + const visualizationMap = { + lnsXY: { + ...mockVis, + switchVisualizationType(seriesType: string, state: unknown) { + return { + ...(state as Record), + preferredSeriesType: seriesType, + }; + }, + getSuggestions: () => [ + { + score: 0.8, + title: 'bar', + state: { + preferredSeriesType: 'bar_stacked', + legend: { + isVisible: true, + position: 'right', + }, + }, + previewIcon: 'empty', + visualizationId: 'lnsXY', + }, + { + score: 0.8, + title: 'Test2', + state: {}, + previewIcon: 'empty', + }, + { + score: 0.8, + title: 'Test2', + state: {}, + previewIcon: 'empty', + incomplete: true, + }, + ], + }, + }; + datasourceMap.textBased.getDatasourceSuggestionsForVisualizeField.mockReturnValue([ + generateSuggestion(), + ]); + datasourceMap.textBased.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ + generateSuggestion(), + ]); + const context = { + dataViewSpec: { + id: 'index1', + title: 'index1', + name: 'DataView', + }, + fieldName: '', + textBasedColumns: textBasedQueryColumns, + query: { + esql: 'FROM "index1" | keep field1, field2', + }, + }; + const suggestions = suggestionsApi({ + context, + dataView, + datasourceMap, + visualizationMap, + preferredChartType: ChartType.XY, + preferredVisAttributes: { + visualizationType: 'lnsXY', + state: { + visualization: { + preferredSeriesType: 'bar_stacked', + legend: { + isVisible: false, + position: 'left', + }, + }, + datasourceStates: { textBased: { layers: {} } }, + }, + } as unknown as TypedLensByValueInput['attributes'], + }); + expect(suggestions?.length).toEqual(1); + expect(suggestions?.[0]).toMatchInlineSnapshot(` + Object { + "datasourceId": "textBased", + "datasourceState": Object { + "layers": Object {}, + }, + "keptLayerIds": Array [], + "title": undefined, + "visualizationId": "lnsXY", + "visualizationState": Object { + "legend": Object { + "isVisible": false, + "position": "left", + }, + "preferredSeriesType": "bar_stacked", + }, + } + `); }); test('filters out the suggestion if exists on excludedVisualizations', async () => { From aa7698ed013f25452c127559c2f122de705f0c5f Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 11:17:57 +0100 Subject: [PATCH 20/38] Adds another FT --- .../apps/discover/esql/_esql_view.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index 7b0504b6c20f3..dc550ac5be93d 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -712,6 +712,58 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const type = await chartSwitcher.getVisibleText(); expect(type).to.be('Line'); }); + + it('should append a where clause by clicking the table without changing the chart type nor the visualization state', async () => { + await discover.selectTextBaseLang(); + const testQuery = `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB`; + await monacoEditor.setCodeEditorValue(testQuery); + + await testSubjects.click('querySubmitButton'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await unifiedFieldList.waitUntilSidebarHasLoaded(); + + // change the type to line + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); + await header.waitUntilLoadingHasFinished(); + await testSubjects.click('lnsChartSwitchPopover'); + await testSubjects.click('lnsChartSwitchPopover_line'); + + // change the color to red + await testSubjects.click('lnsXY_yDimensionPanel'); + const colorPickerInput = await testSubjects.find('~indexPattern-dimension-colorPicker'); + await colorPickerInput.clearValueWithKeyboard(); + await colorPickerInput.type('#ff0000'); + await common.sleep(1000); // give time for debounced components to rerender + + await header.waitUntilLoadingHasFinished(); + await testSubjects.click('lns-indexPattern-dimensionContainerClose'); + await testSubjects.click('applyFlyoutButton'); + + await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 1); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await unifiedFieldList.waitUntilSidebarHasLoaded(); + + const editorValue = await monacoEditor.getCodeEditorValue(); + expect(editorValue).to.eql( + `from logstash-* | sort @timestamp desc | limit 10000 | stats countB = count(bytes) by geo.dest | sort countB\n| WHERE \`geo.dest\`=="BT"` + ); + + // check that the type is still line + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); + await header.waitUntilLoadingHasFinished(); + const chartSwitcher = await testSubjects.find('lnsChartSwitchPopover'); + const type = await chartSwitcher.getVisibleText(); + expect(type).to.be('Line'); + + // check that the color is still red + await testSubjects.click('lnsXY_yDimensionPanel'); + const colorPickerInputAfterFilter = await testSubjects.find( + '~indexPattern-dimension-colorPicker' + ); + expect(await colorPickerInputAfterFilter.getAttribute('value')).to.be('#FF0000'); + }); }); describe('histogram breakdown', () => { From 8d14f4779382b0bc88bc859f80a942ab927d7995 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 11:37:18 +0100 Subject: [PATCH 21/38] Change --- x-pack/plugins/lens/public/lens_suggestions_api/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md b/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md index abae6d508f3ae..5a9bbef55d32a 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md +++ b/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md @@ -1,6 +1,6 @@ # Lens Suggestions API -This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards. +This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards ES|QL charts. ## Overview From 7e52f0c11e8b107ad968cfd17b8e09439963e07a Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 11:38:52 +0100 Subject: [PATCH 22/38] Change file name --- .../public/lens_suggestions_api/readme.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/readme.md diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/readme.md b/x-pack/plugins/lens/public/lens_suggestions_api/readme.md new file mode 100644 index 0000000000000..5a9bbef55d32a --- /dev/null +++ b/x-pack/plugins/lens/public/lens_suggestions_api/readme.md @@ -0,0 +1,77 @@ +# Lens Suggestions API + +This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards ES|QL charts. + +## Overview + +The Lens Suggestions API is designed to provide suggestions for visualizations based on a given ES|QL query. It helps users to quickly find the most relevant visualizations for their data. + +## Getting Started + +To use the Lens Suggestions API, you need to import it from the Lens plugin: + +```typescript +import useAsync from 'react-use/lib/useAsync'; + +const lensHelpersAsync = useAsync(() => { + return lensService?.stateHelperApi() ?? Promise.resolve(null); + }, [lensService]); + + if (lensHelpersAsync.value) { + const suggestionsApi = lensHelpersAsync.value.suggestions; + } +``` + +## The api + +The api returns an array of suggestions. + +#### Parameters + + dataView: DataView; + visualizationMap?: VisualizationMap; + datasourceMap?: DatasourceMap; + excludedVisualizations?: string[]; + preferredChartType?: ChartType; + preferredVisAttributes?: TypedLensByValueInput['attributes']; + +- `context`: The context as descibed by the VisualizeFieldContext. +- `dataView`: The dataView, can be an adhoc one too. For ES|QL you can create a dataview like this + +```typescript +const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; +const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); +``` +Optional parameters: +- `preferredChartType`: Use this if you want the suggestions api to prioritize a specific suggestion type. +- `preferredVisAttributes`: Use this with the preferredChartType if you want to prioritize a specific suggestion type with a non-default visualization state. + +#### Returns + +An array of suggestion objects + +## Example Usage + +```typescript +const abc = new AbortController(); + +const columns = await getESQLQueryColumns({ + esqlQuery, + search: dataService.search.search, + signal: abc.signal, + timeRange: dataService.query.timefilter.timefilter.getAbsoluteTime(), +}); + +const context = { + dataViewSpec: dataView?.toSpec(false), + fieldName: '', + textBasedColumns: columns, + query: { esql: esqlQuery }, +}; + +const chartSuggestions = lensHelpersAsync.value.suggestions(context, dataView); + +suggestions.forEach(suggestion => { + console.log(`Suggestion: ${suggestion.title}, Score: ${suggestion.score}`); +}); +``` \ No newline at end of file From 35d7b419e37ff60fe6ca1c6027f6e6b70cd519f3 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 13:10:19 +0100 Subject: [PATCH 23/38] Delete file --- .../public/lens_suggestions_api/Readme.md | 77 ------------------- .../public/lens_suggestions_api/readme.md | 77 ------------------- 2 files changed, 154 deletions(-) delete mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/Readme.md delete mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/readme.md diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md b/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md deleted file mode 100644 index 5a9bbef55d32a..0000000000000 --- a/x-pack/plugins/lens/public/lens_suggestions_api/Readme.md +++ /dev/null @@ -1,77 +0,0 @@ -# Lens Suggestions API - -This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards ES|QL charts. - -## Overview - -The Lens Suggestions API is designed to provide suggestions for visualizations based on a given ES|QL query. It helps users to quickly find the most relevant visualizations for their data. - -## Getting Started - -To use the Lens Suggestions API, you need to import it from the Lens plugin: - -```typescript -import useAsync from 'react-use/lib/useAsync'; - -const lensHelpersAsync = useAsync(() => { - return lensService?.stateHelperApi() ?? Promise.resolve(null); - }, [lensService]); - - if (lensHelpersAsync.value) { - const suggestionsApi = lensHelpersAsync.value.suggestions; - } -``` - -## The api - -The api returns an array of suggestions. - -#### Parameters - - dataView: DataView; - visualizationMap?: VisualizationMap; - datasourceMap?: DatasourceMap; - excludedVisualizations?: string[]; - preferredChartType?: ChartType; - preferredVisAttributes?: TypedLensByValueInput['attributes']; - -- `context`: The context as descibed by the VisualizeFieldContext. -- `dataView`: The dataView, can be an adhoc one too. For ES|QL you can create a dataview like this - -```typescript -const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; -const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); -``` -Optional parameters: -- `preferredChartType`: Use this if you want the suggestions api to prioritize a specific suggestion type. -- `preferredVisAttributes`: Use this with the preferredChartType if you want to prioritize a specific suggestion type with a non-default visualization state. - -#### Returns - -An array of suggestion objects - -## Example Usage - -```typescript -const abc = new AbortController(); - -const columns = await getESQLQueryColumns({ - esqlQuery, - search: dataService.search.search, - signal: abc.signal, - timeRange: dataService.query.timefilter.timefilter.getAbsoluteTime(), -}); - -const context = { - dataViewSpec: dataView?.toSpec(false), - fieldName: '', - textBasedColumns: columns, - query: { esql: esqlQuery }, -}; - -const chartSuggestions = lensHelpersAsync.value.suggestions(context, dataView); - -suggestions.forEach(suggestion => { - console.log(`Suggestion: ${suggestion.title}, Score: ${suggestion.score}`); -}); -``` \ No newline at end of file diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/readme.md b/x-pack/plugins/lens/public/lens_suggestions_api/readme.md deleted file mode 100644 index 5a9bbef55d32a..0000000000000 --- a/x-pack/plugins/lens/public/lens_suggestions_api/readme.md +++ /dev/null @@ -1,77 +0,0 @@ -# Lens Suggestions API - -This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards ES|QL charts. - -## Overview - -The Lens Suggestions API is designed to provide suggestions for visualizations based on a given ES|QL query. It helps users to quickly find the most relevant visualizations for their data. - -## Getting Started - -To use the Lens Suggestions API, you need to import it from the Lens plugin: - -```typescript -import useAsync from 'react-use/lib/useAsync'; - -const lensHelpersAsync = useAsync(() => { - return lensService?.stateHelperApi() ?? Promise.resolve(null); - }, [lensService]); - - if (lensHelpersAsync.value) { - const suggestionsApi = lensHelpersAsync.value.suggestions; - } -``` - -## The api - -The api returns an array of suggestions. - -#### Parameters - - dataView: DataView; - visualizationMap?: VisualizationMap; - datasourceMap?: DatasourceMap; - excludedVisualizations?: string[]; - preferredChartType?: ChartType; - preferredVisAttributes?: TypedLensByValueInput['attributes']; - -- `context`: The context as descibed by the VisualizeFieldContext. -- `dataView`: The dataView, can be an adhoc one too. For ES|QL you can create a dataview like this - -```typescript -const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; -const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); -``` -Optional parameters: -- `preferredChartType`: Use this if you want the suggestions api to prioritize a specific suggestion type. -- `preferredVisAttributes`: Use this with the preferredChartType if you want to prioritize a specific suggestion type with a non-default visualization state. - -#### Returns - -An array of suggestion objects - -## Example Usage - -```typescript -const abc = new AbortController(); - -const columns = await getESQLQueryColumns({ - esqlQuery, - search: dataService.search.search, - signal: abc.signal, - timeRange: dataService.query.timefilter.timefilter.getAbsoluteTime(), -}); - -const context = { - dataViewSpec: dataView?.toSpec(false), - fieldName: '', - textBasedColumns: columns, - query: { esql: esqlQuery }, -}; - -const chartSuggestions = lensHelpersAsync.value.suggestions(context, dataView); - -suggestions.forEach(suggestion => { - console.log(`Suggestion: ${suggestion.title}, Score: ${suggestion.score}`); -}); -``` \ No newline at end of file From 63784883f45aa2e525136e062ca36a30c449356c Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 13:13:16 +0100 Subject: [PATCH 24/38] Add this again --- .../public/lens_suggestions_api/readme.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 x-pack/plugins/lens/public/lens_suggestions_api/readme.md diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/readme.md b/x-pack/plugins/lens/public/lens_suggestions_api/readme.md new file mode 100644 index 0000000000000..5a9bbef55d32a --- /dev/null +++ b/x-pack/plugins/lens/public/lens_suggestions_api/readme.md @@ -0,0 +1,77 @@ +# Lens Suggestions API + +This document provides an overview of the Lens Suggestions API. It is used mostly for suggesting ES|QL charts based on an ES|QL query. It is used by the observability assistant, Discover and Dashboards ES|QL charts. + +## Overview + +The Lens Suggestions API is designed to provide suggestions for visualizations based on a given ES|QL query. It helps users to quickly find the most relevant visualizations for their data. + +## Getting Started + +To use the Lens Suggestions API, you need to import it from the Lens plugin: + +```typescript +import useAsync from 'react-use/lib/useAsync'; + +const lensHelpersAsync = useAsync(() => { + return lensService?.stateHelperApi() ?? Promise.resolve(null); + }, [lensService]); + + if (lensHelpersAsync.value) { + const suggestionsApi = lensHelpersAsync.value.suggestions; + } +``` + +## The api + +The api returns an array of suggestions. + +#### Parameters + + dataView: DataView; + visualizationMap?: VisualizationMap; + datasourceMap?: DatasourceMap; + excludedVisualizations?: string[]; + preferredChartType?: ChartType; + preferredVisAttributes?: TypedLensByValueInput['attributes']; + +- `context`: The context as descibed by the VisualizeFieldContext. +- `dataView`: The dataView, can be an adhoc one too. For ES|QL you can create a dataview like this + +```typescript +const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; +const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); +``` +Optional parameters: +- `preferredChartType`: Use this if you want the suggestions api to prioritize a specific suggestion type. +- `preferredVisAttributes`: Use this with the preferredChartType if you want to prioritize a specific suggestion type with a non-default visualization state. + +#### Returns + +An array of suggestion objects + +## Example Usage + +```typescript +const abc = new AbortController(); + +const columns = await getESQLQueryColumns({ + esqlQuery, + search: dataService.search.search, + signal: abc.signal, + timeRange: dataService.query.timefilter.timefilter.getAbsoluteTime(), +}); + +const context = { + dataViewSpec: dataView?.toSpec(false), + fieldName: '', + textBasedColumns: columns, + query: { esql: esqlQuery }, +}; + +const chartSuggestions = lensHelpersAsync.value.suggestions(context, dataView); + +suggestions.forEach(suggestion => { + console.log(`Suggestion: ${suggestion.title}, Score: ${suggestion.score}`); +}); +``` \ No newline at end of file From 33a082f74579ae2f3a46f6e0b3f3cf887b3e87a1 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 28 Oct 2024 15:28:03 +0100 Subject: [PATCH 25/38] Update only when necessary --- .../unified_histogram/public/utils/external_vis_context.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.ts index 4c1555bfbcaba..058a3ea3d2636 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.ts @@ -119,7 +119,9 @@ export const injectESQLQueryIntoLensLayers = ( const datasourceState = Object.assign({}, visAttributes.state.datasourceStates[datasourceId]); Object.values(datasourceState.layers).forEach((layer) => { - layer.query = query; + if (!isEqual(layer.query, query)) { + layer.query = query; + } }); return { From 0397b5ff192e54b38f334d43a00d1cc1696b7480 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 29 Oct 2024 10:02:13 +0100 Subject: [PATCH 26/38] Fixes FT --- .../public/utils/external_vis_context.ts | 20 ++++++++++++------- .../apps/discover/group3/_lens_vis.ts | 3 ++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.ts index 058a3ea3d2636..43a7d0ba0a070 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { isEqual } from 'lodash'; +import { isEqual, cloneDeep } from 'lodash'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; @@ -116,14 +116,20 @@ export const injectESQLQueryIntoLensLayers = ( if (!datasourceId || datasourceId === 'formBased') { return visAttributes; } - const datasourceState = Object.assign({}, visAttributes.state.datasourceStates[datasourceId]); - Object.values(datasourceState.layers).forEach((layer) => { - if (!isEqual(layer.query, query)) { - layer.query = query; - } - }); + if (!visAttributes.state.datasourceStates[datasourceId]) { + return visAttributes; + } + const datasourceState = cloneDeep(visAttributes.state.datasourceStates[datasourceId]); + + if (datasourceState && datasourceState.layers) { + Object.values(datasourceState.layers).forEach((layer) => { + if (!isEqual(layer.query, query)) { + layer.query = query; + } + }); + } return { ...visAttributes, state: { diff --git a/test/functional/apps/discover/group3/_lens_vis.ts b/test/functional/apps/discover/group3/_lens_vis.ts index 5e13c8bbb243c..71125e820be57 100644 --- a/test/functional/apps/discover/group3/_lens_vis.ts +++ b/test/functional/apps/discover/group3/_lens_vis.ts @@ -288,7 +288,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); await discover.waitUntilSearchingHasFinished(); - expect(await getCurrentVisTitle()).to.be('Bar'); + // Line has been retained although the query changed! + expect(await getCurrentVisTitle()).to.be('Line'); await checkESQLHistogramVis(defaultTimespanESQL, '100'); From c1dbf42897bcd0c0e903941920427b73c5a9ad48 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 29 Oct 2024 12:57:42 +0100 Subject: [PATCH 27/38] Fix FT --- test/functional/apps/discover/group3/_lens_vis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/group3/_lens_vis.ts b/test/functional/apps/discover/group3/_lens_vis.ts index 71125e820be57..0fe4d8d0cd7e8 100644 --- a/test/functional/apps/discover/group3/_lens_vis.ts +++ b/test/functional/apps/discover/group3/_lens_vis.ts @@ -575,7 +575,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); await discover.waitUntilSearchingHasFinished(); - expect(await getCurrentVisTitle()).to.be('Bar'); + // type stays the same as the columns remain the same + expect(await getCurrentVisTitle()).to.be('Treemap'); expect(await discover.getVisContextSuggestionType()).to.be('lensSuggestion'); await testSubjects.existOrFail('unsavedChangesBadge'); From 9dd1b0df21757bd34bbd4a9a9e739c7dc6a40c9d Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 31 Oct 2024 08:16:02 +0100 Subject: [PATCH 28/38] Test the transition to histogram --- test/functional/apps/discover/group3/_lens_vis.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/functional/apps/discover/group3/_lens_vis.ts b/test/functional/apps/discover/group3/_lens_vis.ts index 0fe4d8d0cd7e8..0864382cad7a8 100644 --- a/test/functional/apps/discover/group3/_lens_vis.ts +++ b/test/functional/apps/discover/group3/_lens_vis.ts @@ -568,16 +568,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('partitionVisChart'); expect(await discover.getVisContextSuggestionType()).to.be('lensSuggestion'); - await monacoEditor.setCodeEditorValue( - 'from logstash-* | stats averageB = avg(bytes) by extension.raw' - ); + // reset to histogram + await monacoEditor.setCodeEditorValue('from logstash-*'); await testSubjects.click('querySubmitButton'); await header.waitUntilLoadingHasFinished(); await discover.waitUntilSearchingHasFinished(); - // type stays the same as the columns remain the same - expect(await getCurrentVisTitle()).to.be('Treemap'); - expect(await discover.getVisContextSuggestionType()).to.be('lensSuggestion'); + expect(await getCurrentVisTitle()).to.be('Bar'); + expect(await discover.getVisContextSuggestionType()).to.be('histogramForESQL'); await testSubjects.existOrFail('unsavedChangesBadge'); From e572ef58d791eab7ede74a54d325d752f436c8dd Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 4 Nov 2024 08:00:48 +0100 Subject: [PATCH 29/38] Change to fit the charttyp --- .../unified_histogram/public/services/lens_vis_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index 0c584eb1a83bd..eb3d3516cf371 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -504,7 +504,7 @@ export class LensVisService { context, dataView, ['lnsDatatable'], - 'lnsXY' as ChartType, + 'XY' as ChartType, updatedAttributesWithQuery ) ?? []; if (suggestions.length) { From de63f5c7f915447c17a99d1051852bb81aa57fda Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 4 Nov 2024 08:25:57 +0100 Subject: [PATCH 30/38] Fixes bug that also exists in main --- .../unified_histogram/public/services/lens_vis_service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index eb3d3516cf371..b5daea6f78172 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -583,6 +583,12 @@ export class LensVisService { ? (preferredVisAttributes?.visualizationType.replace(LENS_PREFIX, '') as ChartType) : undefined; + let visAttributes = preferredVisAttributes; + + if (query && isOfAggregateQueryType(query) && preferredVisAttributes) { + visAttributes = injectESQLQueryIntoLensLayers(preferredVisAttributes, query); + } + const context = { dataViewSpec: dataView?.toSpec(), fieldName: '', @@ -595,7 +601,7 @@ export class LensVisService { dataView, ['lnsDatatable'], preferredChartType, - preferredVisAttributes + visAttributes ) ?? [] : []; From 1e92f8de1c0b67da606261853c0c3ceccb4351d8 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 4 Nov 2024 10:47:42 +0100 Subject: [PATCH 31/38] Apply PR comments --- packages/kbn-visualization-utils/index.ts | 1 + .../src/get_datasource_id.ts | 17 +++++++++++++++++ packages/kbn-visualization-utils/src/types.ts | 5 +++-- .../public/services/lens_vis_service.ts | 4 ++-- .../public/utils/external_vis_context.ts | 11 +++-------- .../lens/public/lens_suggestions_api/helpers.ts | 6 ++---- 6 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 packages/kbn-visualization-utils/src/get_datasource_id.ts diff --git a/packages/kbn-visualization-utils/index.ts b/packages/kbn-visualization-utils/index.ts index fa4a2fa1b6f55..b83aaa8d3cfb5 100644 --- a/packages/kbn-visualization-utils/index.ts +++ b/packages/kbn-visualization-utils/index.ts @@ -12,3 +12,4 @@ export { getLensAttributesFromSuggestion } from './src/get_lens_attributes'; export { TooltipWrapper } from './src/tooltip_wrapper'; export { useDebouncedValue } from './src/debounced_value'; export { ChartType } from './src/types'; +export { getDatasourceId } from './src/get_datasource_id'; diff --git a/packages/kbn-visualization-utils/src/get_datasource_id.ts b/packages/kbn-visualization-utils/src/get_datasource_id.ts new file mode 100644 index 0000000000000..c87d08f8e3e27 --- /dev/null +++ b/packages/kbn-visualization-utils/src/get_datasource_id.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const getDatasourceId = (datasourceStates: Record) => { + const datasourceId: 'formBased' | 'textBased' | undefined = [ + 'formBased' as const, + 'textBased' as const, + ].find((key) => Boolean(datasourceStates[key])); + + return datasourceId; +}; diff --git a/packages/kbn-visualization-utils/src/types.ts b/packages/kbn-visualization-utils/src/types.ts index 493d52472e867..8712ad2d7c063 100644 --- a/packages/kbn-visualization-utils/src/types.ts +++ b/packages/kbn-visualization-utils/src/types.ts @@ -50,9 +50,10 @@ export enum ChartType { Line = 'Line', Area = 'Area', Donut = 'Donut', - Heatmap = 'Heat map', + Heatmap = 'Heatmap', + Metric = 'Metric', Treemap = 'Treemap', - Tagcloud = 'Tag cloud', + Tagcloud = 'Tagcloud', Waffle = 'Waffle', Pie = 'Pie', Mosaic = 'Mosaic', diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index b5daea6f78172..818f2929dd722 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -27,7 +27,7 @@ import type { import type { AggregateQuery, TimeRange } from '@kbn/es-query'; import { getAggregateQueryMode, isOfAggregateQueryType } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { getLensAttributesFromSuggestion, type ChartType } from '@kbn/visualization-utils'; +import { getLensAttributesFromSuggestion, ChartType } from '@kbn/visualization-utils'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { XYConfiguration } from '@kbn/visualizations-plugin/common'; import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; @@ -504,7 +504,7 @@ export class LensVisService { context, dataView, ['lnsDatatable'], - 'XY' as ChartType, + ChartType.XY, updatedAttributesWithQuery ) ?? []; if (suggestions.length) { diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.ts index 43a7d0ba0a070..ef5788b4b25ba 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.ts @@ -10,6 +10,7 @@ import { isEqual, cloneDeep } from 'lodash'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { getDatasourceId } from '@kbn/visualization-utils'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import type { PieVisualizationState, Suggestion, XYState } from '@kbn/lens-plugin/public'; import { UnifiedHistogramSuggestionType, UnifiedHistogramVisContext } from '../types'; @@ -107,10 +108,7 @@ export const injectESQLQueryIntoLensLayers = ( visAttributes: UnifiedHistogramVisContext['attributes'], query: AggregateQuery ) => { - const datasourceId: 'formBased' | 'textBased' | undefined = [ - 'formBased' as const, - 'textBased' as const, - ].find((key) => Boolean(visAttributes.state.datasourceStates[key])); + const datasourceId = getDatasourceId(visAttributes.state.datasourceStates); // if the datasource is formBased, we should not fix the query if (!datasourceId || datasourceId === 'formBased') { @@ -161,10 +159,7 @@ export function deriveLensSuggestionFromLensAttributes({ } // it should be one of 'formBased'/'textBased' and have value - const datasourceId: 'formBased' | 'textBased' | undefined = [ - 'formBased' as const, - 'textBased' as const, - ].find((key) => Boolean(externalVisContext.attributes.state.datasourceStates[key])); + const datasourceId = getDatasourceId(externalVisContext.attributes.state.datasourceStates); if (!datasourceId) { return undefined; diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts index 14a7a5f5d06ba..6a402b07ea05f 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; +import { getDatasourceId } from '@kbn/visualization-utils'; import type { VisualizeEditorContext, Suggestion } from '../types'; import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; @@ -34,10 +35,7 @@ export function mergeSuggestionWithVisContext({ } // it should be one of 'formBased'/'textBased' and have value - const datasourceId: 'formBased' | 'textBased' | undefined = [ - 'formBased' as const, - 'textBased' as const, - ].find((key) => Boolean(visAttributes.state.datasourceStates[key])); + const datasourceId = getDatasourceId(visAttributes.state.datasourceStates); // if the datasource is formBased, we should not merge if (!datasourceId || datasourceId === 'formBased') { From ea3e44335d5435208a68ea74f49ef2d643f32ca7 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 4 Nov 2024 13:17:15 +0100 Subject: [PATCH 32/38] Revert the cleanup --- .../public/services/lens_vis_service.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index 818f2929dd722..b9757f4f95182 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -283,6 +283,33 @@ export class LensVisService { } } + if (externalVisContext && queryParams.isPlainRecord) { + // externalVisContext can be based on an unfamiliar suggestion (not a part of allSuggestions), but it was saved before, so we try to restore it too + const derivedSuggestion = deriveLensSuggestionFromLensAttributes({ + externalVisContext, + queryParams, + }); + + if (derivedSuggestion) { + availableSuggestionsWithType.push({ + suggestion: derivedSuggestion, + type: UnifiedHistogramSuggestionType.lensSuggestion, + }); + } + } + + if (externalVisContext) { + // try to find a suggestion that is compatible with the external vis context + const matchingItem = availableSuggestionsWithType.find((item) => + isSuggestionShapeAndVisContextCompatible(item.suggestion, externalVisContext) + ); + + if (matchingItem) { + currentSuggestion = matchingItem.suggestion; + type = matchingItem.type; + } + } + if (!currentSuggestion && availableSuggestionsWithType.length) { // otherwise pick any first available suggestion currentSuggestion = availableSuggestionsWithType[0].suggestion; From abbe8595869695bcd1e5dbfa24a1e144d9e11eb1 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 5 Nov 2024 11:27:23 +0100 Subject: [PATCH 33/38] Suggestion --- packages/kbn-visualization-utils/index.ts | 1 + .../src/map_vis_to_chart_type.ts | 37 +++++++++++++++++++ packages/kbn-visualization-utils/src/types.ts | 11 ++++++ .../public/services/lens_vis_service.ts | 9 +++-- 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts diff --git a/packages/kbn-visualization-utils/index.ts b/packages/kbn-visualization-utils/index.ts index b83aaa8d3cfb5..1773a04db76d9 100644 --- a/packages/kbn-visualization-utils/index.ts +++ b/packages/kbn-visualization-utils/index.ts @@ -13,3 +13,4 @@ export { TooltipWrapper } from './src/tooltip_wrapper'; export { useDebouncedValue } from './src/debounced_value'; export { ChartType } from './src/types'; export { getDatasourceId } from './src/get_datasource_id'; +export { mapVisToChartType } from './src/map_vis_to_chart_type'; diff --git a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts new file mode 100644 index 0000000000000..a0a18d379b60a --- /dev/null +++ b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ChartType, LensVisualizationType } from './types'; + +export const mapVisToChartType = (visualizationType: string) => { + let chartType: ChartType | undefined; + + switch (visualizationType) { + case LensVisualizationType.XY: + chartType = ChartType.XY; + break; + case LensVisualizationType.Metric: + chartType = ChartType.Metric; + break; + case LensVisualizationType.Pie: + chartType = ChartType.Pie; + break; + case LensVisualizationType.Heatmap: + chartType = ChartType.Heatmap; + break; + case LensVisualizationType.Datatable: + chartType = ChartType.Table; + break; + case LensVisualizationType.LegacyMetric: + chartType = ChartType.Metric; + break; + } + + return chartType; +}; diff --git a/packages/kbn-visualization-utils/src/types.ts b/packages/kbn-visualization-utils/src/types.ts index 8712ad2d7c063..cd73cbea20631 100644 --- a/packages/kbn-visualization-utils/src/types.ts +++ b/packages/kbn-visualization-utils/src/types.ts @@ -46,6 +46,7 @@ export interface Suggestion { export enum ChartType { XY = 'XY', + Gauge = 'Gauge', Bar = 'Bar', Line = 'Line', Area = 'Area', @@ -59,3 +60,13 @@ export enum ChartType { Mosaic = 'Mosaic', Table = 'Table', } + +export enum LensVisualizationType { + XY = 'lnsXY', + Metric = 'lnsMetric', + Pie = 'lnsPie', + Heatmap = 'lnsHeatmap', + Gauge = 'lnsGauge', + Datatable = 'lnsDatatable', + LegacyMetric = 'lnsLegacyMetric', +} diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index b9757f4f95182..2367e729b5a70 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -27,7 +27,11 @@ import type { import type { AggregateQuery, TimeRange } from '@kbn/es-query'; import { getAggregateQueryMode, isOfAggregateQueryType } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { getLensAttributesFromSuggestion, ChartType } from '@kbn/visualization-utils'; +import { + getLensAttributesFromSuggestion, + ChartType, + mapVisToChartType, +} from '@kbn/visualization-utils'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { XYConfiguration } from '@kbn/visualizations-plugin/common'; import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; @@ -50,7 +54,6 @@ import { shouldDisplayHistogram } from '../layout/helpers'; import { enrichLensAttributesWithTablesData } from '../utils/lens_vis_from_table'; const UNIFIED_HISTOGRAM_LAYER_ID = 'unifiedHistogram'; -const LENS_PREFIX = 'lns'; const stateSelectorFactory = (state$: Observable) => @@ -607,7 +610,7 @@ export class LensVisService { const { dataView, columns, query, isPlainRecord } = queryParams; const preferredChartType = preferredVisAttributes - ? (preferredVisAttributes?.visualizationType.replace(LENS_PREFIX, '') as ChartType) + ? mapVisToChartType(preferredVisAttributes.visualizationType) : undefined; let visAttributes = preferredVisAttributes; From 7ee61ab4afba11556c7163fee35579a5a78c2ca1 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 5 Nov 2024 14:18:44 +0100 Subject: [PATCH 34/38] Update packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> --- .../src/map_vis_to_chart_type.ts | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts index a0a18d379b60a..85e7c4ad5375c 100644 --- a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts +++ b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts @@ -10,28 +10,19 @@ import { ChartType, LensVisualizationType } from './types'; export const mapVisToChartType = (visualizationType: string) => { - let chartType: ChartType | undefined; - switch (visualizationType) { case LensVisualizationType.XY: - chartType = ChartType.XY; - break; + return ChartType.XY; case LensVisualizationType.Metric: - chartType = ChartType.Metric; - break; + case LensVisualizationType.LegacyMetric: + return ChartType.Metric; case LensVisualizationType.Pie: - chartType = ChartType.Pie; - break; + return ChartType.Pie; case LensVisualizationType.Heatmap: - chartType = ChartType.Heatmap; - break; + return ChartType.Heatmap; + case LensVisualizationType.Gauge: + return ChartType.Gauge; case LensVisualizationType.Datatable: - chartType = ChartType.Table; - break; - case LensVisualizationType.LegacyMetric: - chartType = ChartType.Metric; - break; + return ChartType.Table; } - - return chartType; }; From 6daa2c81d59f5b46da06ab50ab75891ee9bf4860 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 5 Nov 2024 17:25:42 +0100 Subject: [PATCH 35/38] Apply PR comment --- .../src/map_vis_to_chart_type.ts | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts index 85e7c4ad5375c..de102476c0162 100644 --- a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts +++ b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts @@ -9,20 +9,24 @@ import { ChartType, LensVisualizationType } from './types'; +type ValueOf = T[keyof T]; +type LensToChartMap = { + [K in ValueOf]: ChartType; +}; +const lensTypesToChartTypes: LensToChartMap = { + [LensVisualizationType.XY]: ChartType.XY, + [LensVisualizationType.Metric]: ChartType.Metric, + [LensVisualizationType.LegacyMetric]: ChartType.Metric, + [LensVisualizationType.Pie]: ChartType.Pie, + [LensVisualizationType.Heatmap]: ChartType.Heatmap, + [LensVisualizationType.Gauge]: ChartType.Gauge, + [LensVisualizationType.Datatable]: ChartType.Table, +}; +function isLensVisualizationType(value: string): value is LensVisualizationType { + return value in LensVisualizationType; +} export const mapVisToChartType = (visualizationType: string) => { - switch (visualizationType) { - case LensVisualizationType.XY: - return ChartType.XY; - case LensVisualizationType.Metric: - case LensVisualizationType.LegacyMetric: - return ChartType.Metric; - case LensVisualizationType.Pie: - return ChartType.Pie; - case LensVisualizationType.Heatmap: - return ChartType.Heatmap; - case LensVisualizationType.Gauge: - return ChartType.Gauge; - case LensVisualizationType.Datatable: - return ChartType.Table; + if (isLensVisualizationType(visualizationType)) { + return lensTypesToChartTypes[visualizationType]; } }; From cf707f8b507c9a4ba179833ddc94ae17f27e51e8 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 5 Nov 2024 18:02:44 +0100 Subject: [PATCH 36/38] Fix wrong suggestion --- .../src/map_vis_to_chart_type.ts | 14 +++++++------- packages/kbn-visualization-utils/src/types.ts | 14 +++++++------- .../lens/public/lens_suggestions_api/helpers.ts | 6 +++++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts index de102476c0162..747364226fe3a 100644 --- a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts +++ b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts @@ -14,13 +14,13 @@ type LensToChartMap = { [K in ValueOf]: ChartType; }; const lensTypesToChartTypes: LensToChartMap = { - [LensVisualizationType.XY]: ChartType.XY, - [LensVisualizationType.Metric]: ChartType.Metric, - [LensVisualizationType.LegacyMetric]: ChartType.Metric, - [LensVisualizationType.Pie]: ChartType.Pie, - [LensVisualizationType.Heatmap]: ChartType.Heatmap, - [LensVisualizationType.Gauge]: ChartType.Gauge, - [LensVisualizationType.Datatable]: ChartType.Table, + [LensVisualizationType.lnsXY]: ChartType.XY, + [LensVisualizationType.lnsMetric]: ChartType.Metric, + [LensVisualizationType.lnsLegacyMetric]: ChartType.Metric, + [LensVisualizationType.lnsPie]: ChartType.Pie, + [LensVisualizationType.lnsHeatmap]: ChartType.Heatmap, + [LensVisualizationType.lnsGauge]: ChartType.Gauge, + [LensVisualizationType.lnsDatatable]: ChartType.Table, }; function isLensVisualizationType(value: string): value is LensVisualizationType { return value in LensVisualizationType; diff --git a/packages/kbn-visualization-utils/src/types.ts b/packages/kbn-visualization-utils/src/types.ts index cd73cbea20631..90851c2c24253 100644 --- a/packages/kbn-visualization-utils/src/types.ts +++ b/packages/kbn-visualization-utils/src/types.ts @@ -62,11 +62,11 @@ export enum ChartType { } export enum LensVisualizationType { - XY = 'lnsXY', - Metric = 'lnsMetric', - Pie = 'lnsPie', - Heatmap = 'lnsHeatmap', - Gauge = 'lnsGauge', - Datatable = 'lnsDatatable', - LegacyMetric = 'lnsLegacyMetric', + lnsXY = 'lnsXY', + lnsMetric = 'lnsMetric', + lnsPie = 'lnsPie', + lnsHeatmap = 'lnsHeatmap', + lnsGauge = 'lnsGauge', + lnsDatatable = 'lnsDatatable', + lnsLegacyMetric = 'lnsLegacyMetric', } diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts index 6a402b07ea05f..394d32e8c5bb7 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts @@ -65,7 +65,11 @@ export function mergeSuggestionWithVisContext({ keptLayerIds: layerIds, datasourceState, datasourceId, - } as Suggestion; + columns: suggestion.columns, + changeType: suggestion.changeType, + score: suggestion.score, + previewIcon: suggestion.previewIcon, + }; } catch { return suggestion; } From 16063d192b61a473fd21762367b9115102c48e6f Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 5 Nov 2024 18:05:13 +0100 Subject: [PATCH 37/38] Small change --- .../src/map_vis_to_chart_type.ts | 16 ++++++++-------- packages/kbn-visualization-utils/src/types.ts | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts index 747364226fe3a..288202f4b999f 100644 --- a/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts +++ b/packages/kbn-visualization-utils/src/map_vis_to_chart_type.ts @@ -14,16 +14,16 @@ type LensToChartMap = { [K in ValueOf]: ChartType; }; const lensTypesToChartTypes: LensToChartMap = { - [LensVisualizationType.lnsXY]: ChartType.XY, - [LensVisualizationType.lnsMetric]: ChartType.Metric, - [LensVisualizationType.lnsLegacyMetric]: ChartType.Metric, - [LensVisualizationType.lnsPie]: ChartType.Pie, - [LensVisualizationType.lnsHeatmap]: ChartType.Heatmap, - [LensVisualizationType.lnsGauge]: ChartType.Gauge, - [LensVisualizationType.lnsDatatable]: ChartType.Table, + [LensVisualizationType.XY]: ChartType.XY, + [LensVisualizationType.Metric]: ChartType.Metric, + [LensVisualizationType.LegacyMetric]: ChartType.Metric, + [LensVisualizationType.Pie]: ChartType.Pie, + [LensVisualizationType.Heatmap]: ChartType.Heatmap, + [LensVisualizationType.Gauge]: ChartType.Gauge, + [LensVisualizationType.Datatable]: ChartType.Table, }; function isLensVisualizationType(value: string): value is LensVisualizationType { - return value in LensVisualizationType; + return Object.values(LensVisualizationType).includes(value as LensVisualizationType); } export const mapVisToChartType = (visualizationType: string) => { if (isLensVisualizationType(visualizationType)) { diff --git a/packages/kbn-visualization-utils/src/types.ts b/packages/kbn-visualization-utils/src/types.ts index 90851c2c24253..cd73cbea20631 100644 --- a/packages/kbn-visualization-utils/src/types.ts +++ b/packages/kbn-visualization-utils/src/types.ts @@ -62,11 +62,11 @@ export enum ChartType { } export enum LensVisualizationType { - lnsXY = 'lnsXY', - lnsMetric = 'lnsMetric', - lnsPie = 'lnsPie', - lnsHeatmap = 'lnsHeatmap', - lnsGauge = 'lnsGauge', - lnsDatatable = 'lnsDatatable', - lnsLegacyMetric = 'lnsLegacyMetric', + XY = 'lnsXY', + Metric = 'lnsMetric', + Pie = 'lnsPie', + Heatmap = 'lnsHeatmap', + Gauge = 'lnsGauge', + Datatable = 'lnsDatatable', + LegacyMetric = 'lnsLegacyMetric', } From 4fad39b1269a11f7196e4975e94755d020a0fa33 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 5 Nov 2024 20:43:30 +0100 Subject: [PATCH 38/38] Update snapshots --- .../public/lens_suggestions_api/lens_suggestions_api.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts index 95e0911460eba..e5e60284e4919 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts @@ -391,11 +391,15 @@ describe('suggestionsApi', () => { expect(suggestions?.length).toEqual(1); expect(suggestions?.[0]).toMatchInlineSnapshot(` Object { + "changeType": "unchanged", + "columns": 0, "datasourceId": "textBased", "datasourceState": Object { "layers": Object {}, }, "keptLayerIds": Array [], + "previewIcon": "empty", + "score": 0.8, "title": undefined, "visualizationId": "lnsXY", "visualizationState": Object {