diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx index cb02e3b736663..456f10d89dc89 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx @@ -19,7 +19,7 @@ import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefi import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataTableRecord, EsHitRecord } from '@kbn/discover-utils/types'; -import { buildDataTableRecord } from '@kbn/discover-utils'; +import { buildDataTableRecord, buildDataTableRecordList } from '@kbn/discover-utils'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { setUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/plugin'; @@ -64,19 +64,8 @@ const waitNextUpdate = async (component: ReactWrapper) => { }; describe('Discover flyout', function () { - const mountComponent = async ({ - dataView, - hits, - hitIndex, - query, - }: { - dataView?: DataView; - hits?: DataTableRecord[]; - hitIndex?: number; - query?: Query | AggregateQuery; - }) => { - const onClose = jest.fn(); - const services = { + const getServices = () => { + return { ...discoverServiceMock, filterManager: createFilterManagerMock(), addBasePath: (path: string) => `/base${path}`, @@ -92,22 +81,35 @@ describe('Discover flyout', function () { addSuccess: jest.fn(), }, } as unknown as DiscoverServices; + }; + + const mountComponent = async ({ + dataView, + records, + expandedHit, + query, + services = getServices(), + }: { + dataView?: DataView; + records?: DataTableRecord[]; + expandedHit?: EsHitRecord; + query?: Query | AggregateQuery; + services?: DiscoverServices; + }) => { + const onClose = jest.fn(); setUnifiedDocViewerServices(mockUnifiedDocViewerServices); - const hit = buildDataTableRecord( - hitIndex ? esHitsMock[hitIndex] : (esHitsMock[0] as EsHitRecord), - dataViewMock - ); + const currentRecords = + records || + esHitsMock.map((entry: EsHitRecord) => buildDataTableRecord(entry, dataView || dataViewMock)); const props = { columns: ['date'], dataView: dataView || dataViewMock, - hit, - hits: - hits || - esHitsMock.map((entry: EsHitRecord) => - buildDataTableRecord(entry, dataView || dataViewMock) - ), + hit: expandedHit + ? buildDataTableRecord(expandedHit, dataView || dataViewMock) + : currentRecords[0], + hits: currentRecords, query, onAddColumn: jest.fn(), onClose, @@ -131,6 +133,7 @@ describe('Discover flyout', function () { beforeEach(() => { mockFlyoutCustomization.actions.defaultActions = undefined; mockFlyoutCustomization.Content = undefined; + mockFlyoutCustomization.title = undefined; jest.clearAllMocks(); (useDiscoverCustomization as jest.Mock).mockImplementation(() => mockFlyoutCustomization); @@ -163,14 +166,14 @@ describe('Discover flyout', function () { }); it('displays no document navigation when there are 0 docs available', async () => { - const { component } = await mountComponent({ hits: [] }); + const { component } = await mountComponent({ records: [], expandedHit: esHitsMock[0] }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeFalsy(); }); it('displays no document navigation when the expanded doc is not part of the given docs', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const hits = [ + const records = [ { _index: 'new', _id: '1', @@ -186,7 +189,7 @@ describe('Discover flyout', function () { _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, }, ].map((hit) => buildDataTableRecord(hit, dataViewMock)); - const { component } = await mountComponent({ hits }); + const { component } = await mountComponent({ records, expandedHit: esHitsMock[0] }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeFalsy(); }); @@ -208,14 +211,18 @@ describe('Discover flyout', function () { it('doesnt allow you to navigate to the next doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = await mountComponent({ hitIndex: esHitsMock.length - 1 }); + const { component, props } = await mountComponent({ + expandedHit: esHitsMock[esHitsMock.length - 1], + }); findTestSubject(component, 'pagination-button-next').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); }); it('allows you to navigate to the previous doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = await mountComponent({ hitIndex: esHitsMock.length - 1 }); + const { component, props } = await mountComponent({ + expandedHit: esHitsMock[esHitsMock.length - 1], + }); findTestSubject(component, 'pagination-button-previous').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(1); expect(props.setExpandedDoc.mock.calls[0][0].raw._id).toBe('4'); @@ -470,5 +477,37 @@ describe('Discover flyout', function () { expect(props.onFilter).toHaveBeenCalled(); }); }); + + describe('context awareness', () => { + it('should render flyout per the defined document profile', async () => { + const services = getServices(); + const hits = [ + { + _index: 'new', + _id: '1', + _score: 1, + _type: '_doc', + _source: { date: '2020-20-01T12:12:12.123', message: 'test1', bytes: 20 }, + }, + { + _index: 'new', + _id: '2', + _score: 1, + _type: '_doc', + _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, + }, + ]; + const records = buildDataTableRecordList({ + records: hits as EsHitRecord[], + dataView: dataViewMock, + processRecord: (record) => services.profilesManager.resolveDocumentProfile({ record }), + }); + const { component } = await mountComponent({ records, services }); + const title = findTestSubject(component, 'docTableRowDetailsTitle'); + expect(title.text()).toBe('Document #new::1::'); + const content = findTestSubject(component, 'kbnDocViewer'); + expect(content.text()).toBe('Mock tab'); + }); + }); }); }); diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx index d6273d7669391..891ae384bd41a 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx @@ -31,12 +31,14 @@ import { Filter, Query, AggregateQuery, isOfAggregateQueryType } from '@kbn/es-q import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import type { DataTableColumnsMeta } from '@kbn/unified-data-table'; +import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { useFlyoutActions } from './use_flyout_actions'; import { useDiscoverCustomization } from '../../customizations'; import { DiscoverGridFlyoutActions } from './discover_grid_flyout_actions'; +import { useProfileAccessor } from '../../context_awareness'; export interface DiscoverGridFlyoutProps { savedSearchId?: string; @@ -161,6 +163,29 @@ export function DiscoverGridFlyout({ [onRemoveColumn, services.toastNotifications] ); + const defaultFlyoutTitle = isEsqlQuery + ? i18n.translate('discover.grid.tableRow.docViewerEsqlDetailHeading', { + defaultMessage: 'Result', + }) + : i18n.translate('discover.grid.tableRow.docViewerDetailHeading', { + defaultMessage: 'Document', + }); + + const getDocViewerAccessor = useProfileAccessor('getDocViewer', { + record: actualHit, + }); + const docViewer = useMemo(() => { + const getDocViewer = getDocViewerAccessor(() => ({ + title: flyoutCustomization?.title ?? defaultFlyoutTitle, + docViewsRegistry: (registry: DocViewsRegistry) => + typeof flyoutCustomization?.docViewsRegistry === 'function' + ? flyoutCustomization.docViewsRegistry(registry) + : registry, + })); + + return getDocViewer({ record: actualHit }); + }, [defaultFlyoutTitle, flyoutCustomization, getDocViewerAccessor, actualHit]); + const renderDefaultContent = useCallback( () => ( ), [ @@ -185,7 +210,7 @@ export function DiscoverGridFlyout({ isEsqlQuery, onFilter, removeColumn, - flyoutCustomization?.docViewsRegistry, + docViewer.docViewsRegistry, ] ); @@ -208,15 +233,6 @@ export function DiscoverGridFlyout({ renderDefaultContent() ); - const defaultFlyoutTitle = isEsqlQuery - ? i18n.translate('discover.grid.tableRow.docViewerEsqlDetailHeading', { - defaultMessage: 'Result', - }) - : i18n.translate('discover.grid.tableRow.docViewerDetailHeading', { - defaultMessage: 'Document', - }); - const flyoutTitle = flyoutCustomization?.title ?? defaultFlyoutTitle; - return ( -

{flyoutTitle}

+

{docViewer.title}

{activePage !== -1 && ( diff --git a/src/plugins/discover/public/context_awareness/__mocks__/index.ts b/src/plugins/discover/public/context_awareness/__mocks__/index.ts index 0f8beed5d955f..b493ff43bfbca 100644 --- a/src/plugins/discover/public/context_awareness/__mocks__/index.ts +++ b/src/plugins/discover/public/context_awareness/__mocks__/index.ts @@ -61,6 +61,24 @@ export const createContextAwarenessMocks = () => { ...prev(), rootProfile: () => 'document-profile', })), + getDocViewer: (prev) => (params) => { + const recordId = params.record.id; + const prevValue = prev(params); + return { + title: `${prevValue.title} #${recordId}`, + docViewsRegistry: (registry) => { + registry.add({ + id: 'doc_view_mock', + title: 'Mock tab', + order: 10, + component: () => { + return null; + }, + }); + return prevValue.docViewsRegistry(registry); + }, + }; + }, } as DocumentProfileProvider['profile'], resolve: jest.fn(() => ({ isMatch: true, diff --git a/src/plugins/discover/public/context_awareness/types.ts b/src/plugins/discover/public/context_awareness/types.ts index b612b2ce29907..4bc75e6e1727d 100644 --- a/src/plugins/discover/public/context_awareness/types.ts +++ b/src/plugins/discover/public/context_awareness/types.ts @@ -7,7 +7,19 @@ */ import type { CustomCellRenderer } from '@kbn/unified-data-table'; +import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; +import type { DataTableRecord } from '@kbn/discover-utils'; + +export interface DocViewerExtension { + title: string; + docViewsRegistry: (prevRegistry: DocViewsRegistry) => DocViewsRegistry; +} + +export interface DocViewerExtensionParams { + record: DataTableRecord; +} export interface Profile { getCellRenderers: () => CustomCellRenderer; + getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; }