Skip to content

Commit

Permalink
[Textbased languages] Render Lens suggestions in Discover (#151581)
Browse files Browse the repository at this point in the history
## Summary

Part of #147472

This PR adds the unified histogram on the text based mode in Discover
and depicts Lens suggestions to the users. The users can navigate from
it to Lens for further exploration and adding the viz into a dashboard.


![discover](https://user-images.githubusercontent.com/17003240/222077814-33422fe1-4dc1-4861-b9af-681062412b59.gif)

Some notes:
- Lens now exposes a new api to fetch the suggestions from a text based
query. Later it can also be used to return suggestions for the dataview
mode (the other part of the aforementioned issue)
- Lens visualizations have been updated to return the correct
previewIcon (the majority of them were using the empty icon and not the
icon that represents them). This icon appears on the visualization
selection dropdown
- When Lens suggests a table, then the chart is not displayed (Discover
already offers a table view)
- The legacy metric is excluded from the suggestions as it is not
compatible with the text based languages.
- The text based languages are treated a bit differently on the unified
histogram as they do not work with search sessions.
- This feature is on technical preview, so we can iterate more on that
on later esql milestones. This is the first iteration required for the
milestone 1.
- The ESQL search strategy has a default size of 1000
https://github.com/elastic/kibana/blob/main/src/plugins/data/common/search/expressions/essql.ts#L113
which means that this is the max rows that we retrieve. I am keeping the
default as is irrelevant from the PR. In ESQL this limitation doesn't
exist so I think we are fine.

**Flaky test runner**:
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/1972

### Checklist

- [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]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [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: Davis McPhee <davis.mcphee@elastic.co>
  • Loading branch information
stratoula and davismcphee committed Mar 27, 2023
1 parent a5bf13f commit dec52ef
Show file tree
Hide file tree
Showing 62 changed files with 1,864 additions and 227 deletions.
1 change: 1 addition & 0 deletions src/plugins/discover/public/__mocks__/data_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const buildDataViewMock = ({
getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })),
isTimeNanosBased: () => false,
isPersisted: () => true,
toSpec: () => ({}),
getTimeField: () => {
return dataViewFields.find((field) => field.name === timeFieldName);
},
Expand Down
17 changes: 16 additions & 1 deletion src/plugins/discover/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export function createDiscoverServicesMock(): DiscoverServices {
},
}));
dataPlugin.dataViews = createDiscoverDataViewsMock();
expressionsPlugin.run = jest.fn(() =>
of({
partial: false,
result: {
rows: [],
},
})
) as unknown as typeof expressionsPlugin.run;

return {
core: coreMock.createStart(),
Expand Down Expand Up @@ -152,7 +160,14 @@ export function createDiscoverServicesMock(): DiscoverServices {
savedObjectsTagging: {},
dataViews: dataPlugin.dataViews,
timefilter: dataPlugin.query.timefilter.timefilter,
lens: { EmbeddableComponent: jest.fn(() => null) },
lens: {
EmbeddableComponent: jest.fn(() => null),
stateHelperApi: jest.fn(() => {
return {
suggestions: jest.fn(),
};
}),
},
locator: {
useUrl: jest.fn(() => ''),
navigate: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const DiscoverHistogramLayout = ({
inspectorAdapters,
savedSearchFetch$: stateContainer.dataState.fetch$,
searchSessionId,
isPlainRecord,
...commonProps,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,6 @@ describe('Discover component', () => {
).not.toBeNull();
}, 10000);

test('sql query displays no chart toggle', async () => {
const container = document.createElement('div');
await mountComponent(
dataViewWithTimefieldMock,
false,
{ attachTo: container },
{ sql: 'SELECT * FROM test' },
true
);
expect(
container.querySelector('[data-test-subj="unifiedHistogramChartOptionsToggle"]')
).toBeNull();
});

test('the saved search title h1 gains focus on navigate', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ export const DiscoverMainContent = ({
gutterSize="none"
responsive={false}
>
{!isPlainRecord && (
<EuiFlexItem grow={false}>
<EuiHorizontalRule margin="none" />
<EuiFlexItem grow={false}>
<EuiHorizontalRule margin="none" />
{!isPlainRecord && (
<DocumentViewModeToggle viewMode={viewMode} setDiscoverViewMode={setDiscoverViewMode} />
</EuiFlexItem>
)}
)}
</EuiFlexItem>
{dataState.error && (
<ErrorCallout
title={i18n.translate('discover.documentsErrorTitle', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import React, { ReactElement } from 'react';
import { AggregateQuery, Query } from '@kbn/es-query';
import { buildDataTableRecord } from '../../../../utils/build_data_record';
import { esHits } from '../../../../__mocks__/es_hits';
import { act, renderHook, WrapperComponent } from '@testing-library/react-hooks';
Expand Down Expand Up @@ -38,11 +39,11 @@ import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_message
import type { InspectorAdapters } from '../../hooks/use_inspector';

const mockData = dataPluginMock.createStartContract();
const mockQueryState = {
let mockQueryState = {
query: {
query: 'query',
language: 'kuery',
},
} as Query | AggregateQuery,
filters: [],
time: {
from: 'now-15m',
Expand Down Expand Up @@ -112,19 +113,21 @@ describe('useDiscoverHistogram', () => {
foundDocuments: true,
}) as DataMain$,
savedSearchFetch$ = new Subject() as DataFetch$,
documents$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
result: esHits.map((esHit) => buildDataTableRecord(esHit, dataViewWithTimefieldMock)),
}) as DataDocuments$,
isPlainRecord = false,
}: {
stateContainer?: DiscoverStateContainer;
searchSessionId?: string;
inspectorAdapters?: InspectorAdapters;
totalHits$?: DataTotalHits$;
main$?: DataMain$;
savedSearchFetch$?: DataFetch$;
documents$?: DataDocuments$;
isPlainRecord?: boolean;
} = {}) => {
const documents$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
result: esHits.map((esHit) => buildDataTableRecord(esHit, dataViewWithTimefieldMock)),
}) as DataDocuments$;

const availableFields$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
fields: [] as string[],
Expand All @@ -144,6 +147,7 @@ describe('useDiscoverHistogram', () => {
dataView: dataViewWithTimefieldMock,
inspectorAdapters,
searchSessionId,
isPlainRecord,
};

const Wrapper: WrapperComponent<UseDiscoverHistogramProps> = ({ children }) => (
Expand Down Expand Up @@ -186,6 +190,7 @@ describe('useDiscoverHistogram', () => {
'timeRange',
'chartHidden',
'timeInterval',
'columns',
'breakdownField',
'searchSessionId',
'totalHitsStatus',
Expand All @@ -196,14 +201,17 @@ describe('useDiscoverHistogram', () => {
});

describe('state', () => {
beforeEach(() => {
mockCheckHitCount.mockClear();
});
it('should subscribe to state changes', async () => {
const { hook } = await renderUseDiscoverHistogram();
const api = createMockUnifiedHistogramApi({ initialized: true });
jest.spyOn(api.state$, 'subscribe');
act(() => {
hook.result.current.setUnifiedHistogramApi(api);
});
expect(api.state$.subscribe).toHaveBeenCalledTimes(2);
expect(api.state$.subscribe).toHaveBeenCalledTimes(4);
});

it('should sync Unified Histogram state with the state container', async () => {
Expand All @@ -217,6 +225,7 @@ describe('useDiscoverHistogram', () => {
breakdownField: 'test',
totalHitsStatus: UnifiedHistogramFetchStatus.loading,
totalHitsResult: undefined,
dataView: dataViewWithTimefieldMock,
} as unknown as UnifiedHistogramState;
const api = createMockUnifiedHistogramApi({ initialized: true });
api.state$ = new BehaviorSubject({ ...state, lensRequestAdapter });
Expand All @@ -241,6 +250,7 @@ describe('useDiscoverHistogram', () => {
breakdownField: containerState.breakdownField,
totalHitsStatus: UnifiedHistogramFetchStatus.loading,
totalHitsResult: undefined,
dataView: dataViewWithTimefieldMock,
} as unknown as UnifiedHistogramState;
const api = createMockUnifiedHistogramApi({ initialized: true });
api.state$ = new BehaviorSubject(state);
Expand Down Expand Up @@ -303,6 +313,7 @@ describe('useDiscoverHistogram', () => {
breakdownField: containerState.breakdownField,
totalHitsStatus: UnifiedHistogramFetchStatus.loading,
totalHitsResult: undefined,
dataView: dataViewWithTimefieldMock,
} as unknown as UnifiedHistogramState;
const api = createMockUnifiedHistogramApi({ initialized: true });
let params: Partial<UnifiedHistogramState> = {};
Expand Down Expand Up @@ -347,7 +358,6 @@ describe('useDiscoverHistogram', () => {
});

it('should update total hits when the total hits state changes', async () => {
mockCheckHitCount.mockClear();
const totalHits$ = new BehaviorSubject({
fetchStatus: FetchStatus.LOADING,
result: undefined,
Expand All @@ -366,6 +376,7 @@ describe('useDiscoverHistogram', () => {
breakdownField: containerState.breakdownField,
totalHitsStatus: UnifiedHistogramFetchStatus.loading,
totalHitsResult: undefined,
dataView: dataViewWithTimefieldMock,
} as unknown as UnifiedHistogramState;
const api = createMockUnifiedHistogramApi({ initialized: true });
api.state$ = new BehaviorSubject({
Expand All @@ -384,7 +395,19 @@ describe('useDiscoverHistogram', () => {
});

it('should not update total hits when the total hits state changes to an error', async () => {
mockCheckHitCount.mockClear();
mockQueryState = {
query: {
query: 'query',
language: 'kuery',
} as Query | AggregateQuery,
filters: [],
time: {
from: 'now-15m',
to: 'now',
},
};

mockData.query.getState = () => mockQueryState;
const totalHits$ = new BehaviorSubject({
fetchStatus: FetchStatus.UNINITIALIZED,
result: undefined,
Expand All @@ -399,6 +422,7 @@ describe('useDiscoverHistogram', () => {
breakdownField: containerState.breakdownField,
totalHitsStatus: UnifiedHistogramFetchStatus.loading,
totalHitsResult: undefined,
dataView: dataViewWithTimefieldMock,
} as unknown as UnifiedHistogramState;
const api = createMockUnifiedHistogramApi({ initialized: true });
api.state$ = new BehaviorSubject({
Expand Down
Loading

0 comments on commit dec52ef

Please sign in to comment.