-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Enterprise Search] Add missing pagination to documents (#137998)
* Adds base FE implementation for documents list * Add backend for pagination. * Add tests * Use pagination instead of meta * Use 0 indexed pagination * Fix axe error
- Loading branch information
Showing
11 changed files
with
674 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
...se_search_content/components/search_index/components/document_list/document_list.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* 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; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; | ||
|
||
import React from 'react'; | ||
|
||
import { shallow } from 'enzyme'; | ||
|
||
import { EuiCallOut, EuiPagination } from '@elastic/eui'; | ||
|
||
import { Status } from '../../../../../../../common/types/api'; | ||
|
||
import { Result } from '../../../../../shared/result/result'; | ||
|
||
import { INDEX_DOCUMENTS_META_DEFAULT } from '../../documents_logic'; | ||
|
||
import { DocumentList } from './document_list'; | ||
|
||
const mockActions = {}; | ||
|
||
export const DEFAULT_VALUES = { | ||
data: undefined, | ||
indexName: 'indexName', | ||
isLoading: true, | ||
mappingData: undefined, | ||
mappingStatus: 0, | ||
meta: INDEX_DOCUMENTS_META_DEFAULT, | ||
query: '', | ||
results: [], | ||
status: Status.IDLE, | ||
}; | ||
|
||
const mockValues = { ...DEFAULT_VALUES }; | ||
|
||
describe('DocumentList', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
setMockValues(mockValues); | ||
setMockActions(mockActions); | ||
}); | ||
it('renders empty', () => { | ||
const wrapper = shallow(<DocumentList />); | ||
expect(wrapper.find(Result)).toHaveLength(0); | ||
expect(wrapper.find(EuiPagination)).toHaveLength(2); | ||
}); | ||
|
||
it('renders documents when results when there is data and mappings', () => { | ||
setMockValues({ | ||
...mockValues, | ||
results: [ | ||
{ | ||
_id: 'M9ntXoIBTq5dF-1Xnc8A', | ||
_index: 'kibana_sample_data_flights', | ||
_score: 1, | ||
_source: { | ||
AvgTicketPrice: 268.24159591388866, | ||
}, | ||
}, | ||
{ | ||
_id: 'NNntXoIBTq5dF-1Xnc8A', | ||
_index: 'kibana_sample_data_flights', | ||
_score: 1, | ||
_source: { | ||
AvgTicketPrice: 68.91388866, | ||
}, | ||
}, | ||
], | ||
simplifiedMapping: { | ||
AvgTicketPrice: { | ||
type: 'float', | ||
}, | ||
}, | ||
}); | ||
|
||
const wrapper = shallow(<DocumentList />); | ||
expect(wrapper.find(Result)).toHaveLength(2); | ||
}); | ||
|
||
it('renders callout when total results are 10.000', () => { | ||
setMockValues({ | ||
...mockValues, | ||
meta: { | ||
page: { | ||
...INDEX_DOCUMENTS_META_DEFAULT.page, | ||
total_results: 10000, | ||
}, | ||
}, | ||
}); | ||
const wrapper = shallow(<DocumentList />); | ||
expect(wrapper.find(EuiCallOut)).toHaveLength(1); | ||
}); | ||
}); |
207 changes: 207 additions & 0 deletions
207
...erprise_search_content/components/search_index/components/document_list/document_list.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
/* | ||
* 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; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React, { useState } from 'react'; | ||
|
||
import { useActions, useValues } from 'kea'; | ||
|
||
import { SearchHit } from '@elastic/elasticsearch/lib/api/types'; | ||
|
||
import { | ||
EuiButtonEmpty, | ||
EuiCallOut, | ||
EuiContextMenuItem, | ||
EuiContextMenuPanel, | ||
EuiFlexGroup, | ||
EuiFlexItem, | ||
EuiPagination, | ||
EuiProgress, | ||
EuiPopover, | ||
EuiText, | ||
EuiSpacer, | ||
} from '@elastic/eui'; | ||
|
||
import { i18n } from '@kbn/i18n'; | ||
|
||
import { Result } from '../../../../../shared/result/result'; | ||
|
||
import { DocumentsLogic } from '../../documents_logic'; | ||
|
||
export const DocumentList: React.FC = () => { | ||
const { | ||
docsPerPage, | ||
isLoading, | ||
meta, | ||
results, | ||
simplifiedMapping: mappings, | ||
} = useValues(DocumentsLogic); | ||
const { onPaginate, setDocsPerPage } = useActions(DocumentsLogic); | ||
|
||
const [isPopoverOpen, setIsPopoverOpen] = useState(false); | ||
const resultToField = (result: SearchHit) => { | ||
if (mappings && result._source && !Array.isArray(result._source)) { | ||
if (typeof result._source === 'object') { | ||
return Object.entries(result._source).map(([key, value]) => { | ||
return { | ||
fieldName: key, | ||
fieldType: mappings[key]?.type ?? 'object', | ||
fieldValue: JSON.stringify(value, null, 2), | ||
}; | ||
}); | ||
} | ||
} | ||
return []; | ||
}; | ||
|
||
const docsPerPageButton = ( | ||
<EuiButtonEmpty | ||
size="s" | ||
iconType="arrowDown" | ||
iconSide="right" | ||
onClick={() => { | ||
setIsPopoverOpen(true); | ||
}} | ||
> | ||
{i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.pagination.itemsPerPage', | ||
{ | ||
defaultMessage: 'Documents per page: {docPerPage}', | ||
values: { docPerPage: docsPerPage }, | ||
} | ||
)} | ||
</EuiButtonEmpty> | ||
); | ||
|
||
const getIconType = (size: number) => { | ||
return size === docsPerPage ? 'check' : 'empty'; | ||
}; | ||
|
||
const docsPerPageOptions = [ | ||
<EuiContextMenuItem | ||
key="10 rows" | ||
icon={getIconType(10)} | ||
onClick={() => { | ||
setIsPopoverOpen(false); | ||
setDocsPerPage(10); | ||
}} | ||
> | ||
{i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', | ||
{ defaultMessage: '{docCount} documents', values: { docCount: 10 } } | ||
)} | ||
</EuiContextMenuItem>, | ||
|
||
<EuiContextMenuItem | ||
key="25 rows" | ||
icon={getIconType(25)} | ||
onClick={() => { | ||
setIsPopoverOpen(false); | ||
setDocsPerPage(25); | ||
}} | ||
> | ||
{i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', | ||
{ defaultMessage: '{docCount} documents', values: { docCount: 25 } } | ||
)} | ||
</EuiContextMenuItem>, | ||
<EuiContextMenuItem | ||
key="50 rows" | ||
icon={getIconType(50)} | ||
onClick={() => { | ||
setIsPopoverOpen(false); | ||
setDocsPerPage(50); | ||
}} | ||
> | ||
{i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationOptions.option', | ||
{ defaultMessage: '{docCount} documents', values: { docCount: 50 } } | ||
)} | ||
</EuiContextMenuItem>, | ||
]; | ||
|
||
return ( | ||
<> | ||
<EuiPagination | ||
aria-label={i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationAriaLabel', | ||
{ defaultMessage: 'Pagination for document list' } | ||
)} | ||
pageCount={meta.page.total_pages} | ||
activePage={meta.page.current} | ||
onPageClick={onPaginate} | ||
/> | ||
<EuiSpacer size="m" /> | ||
<EuiText size="xs"> | ||
<p> | ||
Showing <strong>{results.length}</strong> of <strong>{meta.page.total_results}</strong>. | ||
Search results maxed at 10.000 documents. | ||
</p> | ||
</EuiText> | ||
{isLoading && <EuiProgress size="xs" color="primary" />} | ||
<EuiSpacer size="m" /> | ||
{results.map((result) => { | ||
return ( | ||
<React.Fragment key={result._id}> | ||
<Result | ||
fields={resultToField(result)} | ||
metaData={{ | ||
id: result._id, | ||
}} | ||
/> | ||
<EuiSpacer size="s" /> | ||
</React.Fragment> | ||
); | ||
})} | ||
|
||
<EuiFlexGroup justifyContent="spaceBetween"> | ||
<EuiFlexItem grow={false}> | ||
<EuiPagination | ||
aria-label={i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.paginationAriaLabel', | ||
{ defaultMessage: 'Pagination for document list' } | ||
)} | ||
pageCount={meta.page.total_pages} | ||
activePage={meta.page.current} | ||
onPageClick={onPaginate} | ||
/> | ||
</EuiFlexItem> | ||
<EuiFlexItem grow={false}> | ||
<EuiPopover | ||
aria-label={i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.docsPerPage', | ||
{ defaultMessage: 'Document count per page dropdown' } | ||
)} | ||
button={docsPerPageButton} | ||
isOpen={isPopoverOpen} | ||
closePopover={() => { | ||
setIsPopoverOpen(false); | ||
}} | ||
panelPaddingSize="none" | ||
anchorPosition="downLeft" | ||
> | ||
<EuiContextMenuPanel size="s" items={docsPerPageOptions} /> | ||
</EuiPopover> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
|
||
<EuiSpacer /> | ||
{meta.page.total_results === 10000 && ( | ||
<EuiCallOut size="s" title="Results are limited to 10.000 documents" iconType="search"> | ||
<p> | ||
{i18n.translate( | ||
'xpack.enterpriseSearch.content.searchIndex.documents.documentList.resultLimit', | ||
{ | ||
defaultMessage: | ||
'Only the first 10,000 results are available for paging. Please use the search bar to filter down your results.', | ||
} | ||
)} | ||
</p> | ||
</EuiCallOut> | ||
)} | ||
</> | ||
); | ||
}; |
Oops, something went wrong.