Skip to content

Commit

Permalink
[Discover][Extension] Document Viewer (#185940)
Browse files Browse the repository at this point in the history
- Closes #184084

## Summary

This PR adds extension points to Doc Viewer flyout:
- `title`
-  `docViewsRegistry`.

Example:
```typescript
profile: {
    getDocViewer: (prev) => (params) => {
      const recordId = params.record.id;
      const prevValue = prev(params);
      return {
        title: `${prevValue.title} #${recordId}`,
        docViewsRegistry: (registry) => {
          registry.enableById('doc_view_logs_overview');
          return prevValue.docViewsRegistry(registry);
        },
      };
    },
  },
```


### Checklist

- [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
  • Loading branch information
jughosta authored Jun 17, 2024
1 parent d243e4f commit e78c722
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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}`,
Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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',
Expand All @@ -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();
});
Expand All @@ -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');
Expand Down Expand Up @@ -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');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
() => (
<UnifiedDocViewer
Expand All @@ -172,7 +197,7 @@ export function DiscoverGridFlyout({
onAddColumn={addColumn}
onRemoveColumn={removeColumn}
textBasedHits={isEsqlQuery ? hits : undefined}
docViewsRegistry={flyoutCustomization?.docViewsRegistry}
docViewsRegistry={docViewer.docViewsRegistry}
/>
),
[
Expand All @@ -185,7 +210,7 @@ export function DiscoverGridFlyout({
isEsqlQuery,
onFilter,
removeColumn,
flyoutCustomization?.docViewsRegistry,
docViewer.docViewsRegistry,
]
);

Expand All @@ -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 (
<EuiPortal>
<EuiFlyoutResizable
Expand Down Expand Up @@ -252,7 +268,7 @@ export function DiscoverGridFlyout({
white-space: nowrap;
`}
>
<h2>{flyoutTitle}</h2>
<h2>{docViewer.title}</h2>
</EuiTitle>
</EuiFlexItem>
{activePage !== -1 && (
Expand Down
18 changes: 18 additions & 0 deletions src/plugins/discover/public/context_awareness/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions src/plugins/discover/public/context_awareness/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit e78c722

Please sign in to comment.