Skip to content

Commit

Permalink
[Obs ai assistant][ESQL] Visualizes a query (elastic#174677)
Browse files Browse the repository at this point in the history
## Summary


This PR

1. Adds a new CTA (Visualize query) on the generated ES|QL queries
<img width="965" alt="image"
src="https://github.com/elastic/kibana/assets/17003240/3ec3176a-23e1-4329-9d27-a01c6ff8aa92">

2. Clicking the CTA, requests from Lens to suggest a chart based on the
given query
<img width="955" alt="image"
src="https://github.com/elastic/kibana/assets/17003240/466da7d8-f6c4-4c46-9b51-a7fad5e31e55">


3. The embeddable has 2 actions:
- Edit the embeddable
- Save the embeddable on a dashboard

4. Editing the embeddable opens a push flyout where the user can

- Change the query
- Change the chart configuration (colors, dimensions etc)
- Click one of the chart suggestions


![ai_assistant](https://github.com/elastic/kibana/assets/17003240/11fb6a55-60a6-491c-9540-060bebdfaa4a)

5. With clicking the apply button, the new chart configuration is saved
to the conversation

6. User can save the ES|QL chart on a dashboard. From there they can
continue editing the chart (we also display the same inline editing
flyout giving a seamless experience)

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Dario Gieselaar <[email protected]>
Co-authored-by: Milton Hultgren <[email protected]>
Co-authored-by: Coen Warmer <[email protected]>
  • Loading branch information
5 people authored and fkanout committed Feb 7, 2024
1 parent 85475fd commit f7ff1e2
Show file tree
Hide file tree
Showing 147 changed files with 1,839 additions and 466 deletions.
1 change: 1 addition & 0 deletions .i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"visTypeXy": "src/plugins/vis_types/xy",
"visualizations": "src/plugins/visualizations",
"visualizationUiComponents": "packages/kbn-visualization-ui-components",
"visualizationUtils": "packages/kbn-visualization-utils",
"unifiedDocViewer": ["src/plugins/unified_doc_viewer", "packages/kbn-unified-doc-viewer"],
"unifiedSearch": "src/plugins/unified_search",
"unifiedFieldList": "packages/kbn-unified-field-list",
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-field-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
getKbnFieldType,
getKbnTypeNames,
getFilterableKbnTypeNames,
esFieldTypeToKibanaFieldType,
} from './src/kbn_field_types';

export type { KbnFieldTypeOptions } from './src/types';
Expand Down
16 changes: 15 additions & 1 deletion packages/kbn-field-types/src/kbn_field_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { createKbnFieldTypes, kbnFieldTypeUnknown } from './kbn_field_types_factory';
import { KbnFieldType } from './kbn_field_type';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from './types';
Expand Down Expand Up @@ -49,3 +48,18 @@ export const castEsToKbnFieldTypeName = (esType: ES_FIELD_TYPES | string): KBN_F
*/
export const getFilterableKbnTypeNames = (): string[] =>
registeredKbnTypes.filter((type) => type.filterable).map((type) => type.name);

export function esFieldTypeToKibanaFieldType(type: string) {
switch (type) {
case ES_FIELD_TYPES._INDEX:
case ES_FIELD_TYPES.GEO_POINT:
case ES_FIELD_TYPES.IP:
return KBN_FIELD_TYPES.STRING;
case '_version':
return KBN_FIELD_TYPES.NUMBER;
case 'datetime':
return KBN_FIELD_TYPES.DATE;
default:
return castEsToKbnFieldTypeName(type);
}
}
134 changes: 134 additions & 0 deletions packages/kbn-visualization-utils/__mocks__/suggestions_mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import type { TableChangeType } from '../src/types';

export const currentSuggestionMock = {
title: 'Heat map',
hide: false,
score: 0.6,
previewIcon: 'heatmap',
visualizationId: 'lnsHeatmap',
visualizationState: {
shape: 'heatmap',
layerId: '46aa21fa-b747-4543-bf90-0b40007c546d',
layerType: 'data',
legend: {
isVisible: true,
position: 'right',
type: 'heatmap_legend',
},
gridConfig: {
type: 'heatmap_grid',
isCellLabelVisible: false,
isYAxisLabelVisible: true,
isXAxisLabelVisible: true,
isYAxisTitleVisible: false,
isXAxisTitleVisible: false,
},
valueAccessor: '5b9b8b76-0836-4a12-b9c0-980c9900502f',
xAccessor: '81e332d6-ee37-42a8-a646-cea4fc75d2d3',
},
keptLayerIds: ['46aa21fa-b747-4543-bf90-0b40007c546d'],
datasourceState: {
layers: {
'46aa21fa-b747-4543-bf90-0b40007c546d': {
index: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
query: {
esql: 'FROM kibana_sample_data_flights | keep Dest, AvgTicketPrice',
},
columns: [
{
columnId: '81e332d6-ee37-42a8-a646-cea4fc75d2d3',
fieldName: 'Dest',
meta: {
type: 'string',
},
},
{
columnId: '5b9b8b76-0836-4a12-b9c0-980c9900502f',
fieldName: 'AvgTicketPrice',
meta: {
type: 'number',
},
},
],
timeField: 'timestamp',
},
},
indexPatternRefs: [],
initialContext: {
dataViewSpec: {
id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
version: 'WzM1ODA3LDFd',
title: 'kibana_sample_data_flights',
timeFieldName: 'timestamp',
sourceFilters: [],
fields: {
AvgTicketPrice: {
count: 0,
name: 'AvgTicketPrice',
type: 'number',
esTypes: ['float'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
format: {
id: 'number',
params: {
pattern: '$0,0.[00]',
},
},
shortDotsEnable: false,
isMapped: true,
},
Dest: {
count: 0,
name: 'Dest',
type: 'string',
esTypes: ['keyword'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
format: {
id: 'string',
},
shortDotsEnable: false,
isMapped: true,
},
timestamp: {
count: 0,
name: 'timestamp',
type: 'date',
esTypes: ['date'],
scripted: false,
searchable: true,
aggregatable: true,
readFromDocValues: true,
format: {
id: 'date',
},
shortDotsEnable: false,
isMapped: true,
},
},
allowNoIndex: false,
name: 'Kibana Sample Data Flights',
},
fieldName: '',
contextualFields: ['Dest', 'AvgTicketPrice'],
query: {
esql: 'FROM "kibana_sample_data_flights"',
},
},
},
datasourceId: 'textBased',
columns: 2,
changeType: 'initial' as TableChangeType,
};
1 change: 1 addition & 0 deletions packages/kbn-visualization-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
*/

export { getTimeZone } from './src/get_timezone';
export { getLensAttributesFromSuggestion } from './src/get_lens_attributes';
export { TooltipWrapper } from './src/tooltip_wrapper';
50 changes: 50 additions & 0 deletions packages/kbn-visualization-utils/src/get_lens_attributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import { getLensAttributesFromSuggestion } from './get_lens_attributes';
import { AggregateQuery } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { currentSuggestionMock } from '../__mocks__/suggestions_mock';

describe('getLensAttributesFromSuggestion', () => {
const dataView = {
id: `index-pattern-with-timefield-id`,
title: `index-pattern-with-timefield-title`,
fields: [],
getFieldByName: jest.fn(),
timeFieldName: '@timestamp',
isPersisted: () => false,
toSpec: () => ({}),
} as unknown as DataView;
const query: AggregateQuery = { esql: 'from foo | limit 10' };

it('should return correct attributes for given suggestion', () => {
const lensAttrs = getLensAttributesFromSuggestion({
filters: [],
query,
dataView,
suggestion: currentSuggestionMock,
});
expect(lensAttrs).toEqual({
state: expect.objectContaining({
adHocDataViews: {
'index-pattern-with-timefield-id': {},
},
}),
references: [
{
id: 'index-pattern-with-timefield-id',
name: 'textBasedLanguages-datasource-layer-suggestion',
type: 'index-pattern',
},
],
title: currentSuggestionMock.title,
visualizationType: 'lnsHeatmap',
});
});
});
64 changes: 64 additions & 0 deletions packages/kbn-visualization-utils/src/get_lens_attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { AggregateQuery, Query, Filter } from '@kbn/es-query';
import type { Suggestion } from './types';

export const getLensAttributesFromSuggestion = ({
filters,
query,
suggestion,
dataView,
}: {
filters: Filter[];
query: Query | AggregateQuery;
suggestion: Suggestion | undefined;
dataView?: DataView;
}) => {
const suggestionDatasourceState = Object.assign({}, suggestion?.datasourceState);
const suggestionVisualizationState = Object.assign({}, suggestion?.visualizationState);
const datasourceStates =
suggestion && suggestion.datasourceState
? {
[suggestion.datasourceId!]: {
...suggestionDatasourceState,
},
}
: {
formBased: {},
};
const visualization = suggestionVisualizationState;
const attributes = {
title: suggestion
? suggestion.title
: i18n.translate('visualizationUtils.config.suggestion.title', {
defaultMessage: 'New suggestion',
}),
references: [
{
id: dataView?.id ?? '',
name: `textBasedLanguages-datasource-layer-suggestion`,
type: 'index-pattern',
},
],
state: {
datasourceStates,
filters,
query,
visualization,
...(dataView &&
dataView.id &&
!dataView.isPersisted() && {
adHocDataViews: { [dataView.id]: dataView.toSpec(false) },
}),
},
visualizationType: suggestion ? suggestion.visualizationId : 'lnsXY',
};
return attributes;
};
43 changes: 43 additions & 0 deletions packages/kbn-visualization-utils/src/types.ts
Original file line number Diff line number Diff line change
@@ -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 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 or the Server
* Side Public License, v 1.
*/
import type { Ast } from '@kbn/interpreter';
import type { IconType } from '@elastic/eui/src/components/icon/icon';

/**
* Indicates what was changed in this table compared to the currently active table of this layer.
* * `initial` means the layer associated with this table does not exist in the current configuration
* * `unchanged` means the table is the same in the currently active configuration
* * `reduced` means the table is a reduced version of the currently active table (some columns dropped, but not all of them)
* * `extended` means the table is an extended version of the currently active table (added one or multiple additional columns)
* * `reorder` means the table columns have changed order, which change the data as well
* * `layers` means the change is a change to the layer structure, not to the table
*/
export type TableChangeType =
| 'initial'
| 'unchanged'
| 'reduced'
| 'extended'
| 'reorder'
| 'layers';

export interface Suggestion<T = unknown, V = unknown> {
visualizationId: string;
datasourceState?: V;
datasourceId?: string;
columns: number;
score: number;
title: string;
visualizationState: T;
previewExpression?: Ast | string;
previewIcon: IconType;
hide?: boolean;
// flag to indicate if the visualization is incomplete
incomplete?: boolean;
changeType: TableChangeType;
keptLayerIds: string[];
}
4 changes: 4 additions & 0 deletions packages/kbn-visualization-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@
],
"kbn_references": [
"@kbn/core",
"@kbn/i18n",
"@kbn/interpreter",
"@kbn/data-views-plugin",
"@kbn/es-query",
]
}
Loading

0 comments on commit f7ff1e2

Please sign in to comment.