[Discover][EuiDataGrid] Add document navigation to flyout (#94439)
- allows the user to navigate to the previous and next document of the list in the document flyout Co-authored-by: Ryan Keairns <contactryank@gmail.com>
This commit is contained in:
parent
76b55207f7
commit
6a85841289
|
@ -40,6 +40,10 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dscTable__flyoutDocumentNavigation {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// We only truncate if the cell is not a control column.
|
||||
.euiDataGridHeader {
|
||||
.euiDataGridHeaderCell__content {
|
||||
|
@ -78,3 +82,10 @@
|
|||
.dscDiscoverGrid__descriptionListDescription {
|
||||
word-break: normal !important;
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
// EUI issue to hide 'of' text https://github.com/elastic/eui/issues/4654
|
||||
.dscTable__flyoutDocumentNavigation .euiPagination__compressedText {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -324,12 +324,14 @@ export const DiscoverGrid = ({
|
|||
<DiscoverGridFlyout
|
||||
indexPattern={indexPattern}
|
||||
hit={expandedDoc}
|
||||
hits={rows}
|
||||
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
|
||||
columns={defaultColumns ? [] : displayedColumns}
|
||||
onFilter={onFilter}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onAddColumn={onAddColumn}
|
||||
onClose={() => setExpandedDoc(undefined)}
|
||||
setExpandedDoc={setExpandedDoc}
|
||||
services={services}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -21,51 +21,41 @@ import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_
|
|||
describe('Discover flyout', function () {
|
||||
setDocViewsRegistry(new DocViewsRegistry());
|
||||
|
||||
it('should be rendered correctly using an index pattern without timefield', async () => {
|
||||
const getProps = () => {
|
||||
const onClose = jest.fn();
|
||||
const component = mountWithIntl(
|
||||
<DiscoverGridFlyout
|
||||
columns={['date']}
|
||||
indexPattern={indexPatternMock}
|
||||
hit={esHits[0]}
|
||||
onAddColumn={jest.fn()}
|
||||
onClose={onClose}
|
||||
onFilter={jest.fn()}
|
||||
onRemoveColumn={jest.fn()}
|
||||
services={
|
||||
({
|
||||
filterManager: createFilterManagerMock(),
|
||||
addBasePath: (path: string) => path,
|
||||
} as unknown) as DiscoverServices
|
||||
}
|
||||
/>
|
||||
);
|
||||
const services = ({
|
||||
filterManager: createFilterManagerMock(),
|
||||
addBasePath: (path: string) => `/base${path}`,
|
||||
} as unknown) as DiscoverServices;
|
||||
|
||||
return {
|
||||
columns: ['date'],
|
||||
indexPattern: indexPatternMock,
|
||||
hit: esHits[0],
|
||||
hits: esHits,
|
||||
onAddColumn: jest.fn(),
|
||||
onClose,
|
||||
onFilter: jest.fn(),
|
||||
onRemoveColumn: jest.fn(),
|
||||
services,
|
||||
setExpandedDoc: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
it('should be rendered correctly using an index pattern without timefield', async () => {
|
||||
const props = getProps();
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
|
||||
const url = findTestSubject(component, 'docTableRowAction').prop('href');
|
||||
expect(url).toMatchInlineSnapshot(`"#/doc/the-index-pattern-id/i?id=1"`);
|
||||
expect(url).toMatchInlineSnapshot(`"/base#/doc/the-index-pattern-id/i?id=1"`);
|
||||
findTestSubject(component, 'euiFlyoutCloseButton').simulate('click');
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be rendered correctly using an index pattern with timefield', async () => {
|
||||
const onClose = jest.fn();
|
||||
const component = mountWithIntl(
|
||||
<DiscoverGridFlyout
|
||||
columns={['date']}
|
||||
indexPattern={indexPatternWithTimefieldMock}
|
||||
hit={esHits[0]}
|
||||
onAddColumn={jest.fn()}
|
||||
onClose={onClose}
|
||||
onFilter={jest.fn()}
|
||||
onRemoveColumn={jest.fn()}
|
||||
services={
|
||||
({
|
||||
filterManager: createFilterManagerMock(),
|
||||
addBasePath: (path: string) => `/base${path}`,
|
||||
} as unknown) as DiscoverServices
|
||||
}
|
||||
/>
|
||||
);
|
||||
const props = getProps();
|
||||
props.indexPattern = indexPatternWithTimefieldMock;
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
|
||||
const actions = findTestSubject(component, 'docTableRowAction');
|
||||
expect(actions.length).toBe(2);
|
||||
|
@ -76,6 +66,81 @@ describe('Discover flyout', function () {
|
|||
`"/base/app/discover#/context/index-pattern-with-timefield-id/1?_g=(filters:!())&_a=(columns:!(date),filters:!())"`
|
||||
);
|
||||
findTestSubject(component, 'euiFlyoutCloseButton').simulate('click');
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('displays document navigation when there is more than 1 doc available', async () => {
|
||||
const props = getProps();
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
const docNav = findTestSubject(component, 'dscDocNavigation');
|
||||
expect(docNav.length).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays no document navigation when there are 0 docs available', async () => {
|
||||
const props = getProps();
|
||||
props.hits = [];
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
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 props = getProps();
|
||||
props.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 component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
const docNav = findTestSubject(component, 'dscDocNavigation');
|
||||
expect(docNav.length).toBeFalsy();
|
||||
});
|
||||
|
||||
it('allows you to navigate to the next doc, if expanded doc is the first', async () => {
|
||||
// scenario: you've expanded a doc, and in the next request different docs where fetched
|
||||
const props = getProps();
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
findTestSubject(component, 'pagination-button-next').simulate('click');
|
||||
// we selected 1, so we'd expect 2
|
||||
expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('2');
|
||||
});
|
||||
|
||||
it('doesnt allow you to navigate to the previous doc, if expanded doc is the first', async () => {
|
||||
// scenario: you've expanded a doc, and in the next request differed docs where fetched
|
||||
const props = getProps();
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
findTestSubject(component, 'pagination-button-previous').simulate('click');
|
||||
expect(props.setExpandedDoc).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
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 props = getProps();
|
||||
props.hit = props.hits[props.hits.length - 1];
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
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 props = getProps();
|
||||
props.hit = props.hits[props.hits.length - 1];
|
||||
const component = mountWithIntl(<DiscoverGridFlyout {...props} />);
|
||||
findTestSubject(component, 'pagination-button-previous').simulate('click');
|
||||
expect(props.setExpandedDoc).toHaveBeenCalledTimes(1);
|
||||
expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('4');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -19,6 +19,8 @@ import {
|
|||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiPortal,
|
||||
EuiPagination,
|
||||
EuiHideFor,
|
||||
} from '@elastic/eui';
|
||||
import { DocViewer } from '../doc_viewer/doc_viewer';
|
||||
import { IndexPattern } from '../../../kibana_services';
|
||||
|
@ -29,19 +31,34 @@ import { getContextUrl } from '../../helpers/get_context_url';
|
|||
interface Props {
|
||||
columns: string[];
|
||||
hit: ElasticSearchHit;
|
||||
hits?: ElasticSearchHit[];
|
||||
indexPattern: IndexPattern;
|
||||
onAddColumn: (column: string) => void;
|
||||
onClose: () => void;
|
||||
onFilter: DocViewFilterFn;
|
||||
onRemoveColumn: (column: string) => void;
|
||||
services: DiscoverServices;
|
||||
setExpandedDoc: (doc: ElasticSearchHit) => void;
|
||||
}
|
||||
|
||||
type ElasticSearchHitWithRouting = ElasticSearchHit & { _routing?: string };
|
||||
|
||||
function getDocFingerprintId(doc: ElasticSearchHitWithRouting) {
|
||||
const routing = doc._routing || '';
|
||||
return [doc._index, doc._id, routing].join('||');
|
||||
}
|
||||
|
||||
function getIndexByDocId(hits: ElasticSearchHit[], id: string) {
|
||||
return hits.findIndex((h) => {
|
||||
return getDocFingerprintId(h) === id;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Flyout displaying an expanded Elasticsearch document
|
||||
*/
|
||||
export function DiscoverGridFlyout({
|
||||
hit,
|
||||
hits,
|
||||
indexPattern,
|
||||
columns,
|
||||
onFilter,
|
||||
|
@ -49,7 +66,27 @@ export function DiscoverGridFlyout({
|
|||
onRemoveColumn,
|
||||
onAddColumn,
|
||||
services,
|
||||
setExpandedDoc,
|
||||
}: Props) {
|
||||
const pageCount = useMemo<number>(() => (hits ? hits.length : 0), [hits]);
|
||||
const activePage = useMemo<number>(() => {
|
||||
const id = getDocFingerprintId(hit);
|
||||
if (!hits || pageCount <= 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return getIndexByDocId(hits, id);
|
||||
}, [hits, hit, pageCount]);
|
||||
|
||||
const setPage = useCallback(
|
||||
(pageIdx: number) => {
|
||||
if (hits && hits[pageIdx]) {
|
||||
setExpandedDoc(hits[pageIdx]);
|
||||
}
|
||||
},
|
||||
[hits, setExpandedDoc]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPortal>
|
||||
<EuiFlyout onClose={onClose} size="m" data-test-subj="docTableDetailsFlyout">
|
||||
|
@ -67,20 +104,23 @@ export function DiscoverGridFlyout({
|
|||
</EuiTitle>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup responsive={false} gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>
|
||||
{i18n.translate('discover.grid.tableRow.viewText', {
|
||||
defaultMessage: 'View:',
|
||||
})}
|
||||
</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup responsive={false} gutterSize="s" alignItems="center">
|
||||
<EuiHideFor sizes={['xs', 's', 'm']}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>
|
||||
{i18n.translate('discover.grid.tableRow.viewText', {
|
||||
defaultMessage: 'View:',
|
||||
})}
|
||||
</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiHideFor>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="document"
|
||||
flush="left"
|
||||
href={services.addBasePath(
|
||||
`#/doc/${indexPattern.id}/${hit._index}?id=${encodeURIComponent(
|
||||
hit._id as string
|
||||
|
@ -98,6 +138,7 @@ export function DiscoverGridFlyout({
|
|||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="documents"
|
||||
flush="left"
|
||||
href={getContextUrl(
|
||||
hit._id,
|
||||
indexPattern.id,
|
||||
|
@ -113,6 +154,21 @@ export function DiscoverGridFlyout({
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{activePage !== -1 && (
|
||||
<EuiFlexItem>
|
||||
<EuiPagination
|
||||
aria-label={i18n.translate('discover.grid.flyout.documentNavigation', {
|
||||
defaultMessage: 'Document navigation',
|
||||
})}
|
||||
pageCount={pageCount}
|
||||
activePage={activePage}
|
||||
onPageClick={setPage}
|
||||
className="dscTable__flyoutDocumentNavigation"
|
||||
compressed
|
||||
data-test-subj="dscDocNavigation"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
|
|
Loading…
Reference in a new issue