mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Discover][EuiDataGrid] Add document selector (#94804)
Co-authored-by: Ryan Keairns <rkeairns@chef.io>
This commit is contained in:
parent
73ccf7844a
commit
8cce4805d4
11 changed files with 537 additions and 11 deletions
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { ReactWrapper } from 'enzyme';
|
||||||
|
import { EuiCopy } from '@elastic/eui';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||||
|
import { esHits } from '../../../__mocks__/es_hits';
|
||||||
|
import { indexPatternMock } from '../../../__mocks__/index_pattern';
|
||||||
|
import { mountWithIntl } from '@kbn/test/jest';
|
||||||
|
import { DiscoverGrid, DiscoverGridProps } from './discover_grid';
|
||||||
|
import { uiSettingsMock } from '../../../__mocks__/ui_settings';
|
||||||
|
import { DiscoverServices } from '../../../build_services';
|
||||||
|
import { ElasticSearchHit } from '../../doc_views/doc_views_types';
|
||||||
|
import { getDocId } from './discover_grid_document_selection';
|
||||||
|
|
||||||
|
function getProps() {
|
||||||
|
const servicesMock = {
|
||||||
|
uiSettings: uiSettingsMock,
|
||||||
|
} as DiscoverServices;
|
||||||
|
return {
|
||||||
|
ariaLabelledBy: '',
|
||||||
|
columns: [],
|
||||||
|
indexPattern: indexPatternMock,
|
||||||
|
isLoading: false,
|
||||||
|
expandedDoc: undefined,
|
||||||
|
onAddColumn: jest.fn(),
|
||||||
|
onFilter: jest.fn(),
|
||||||
|
onRemoveColumn: jest.fn(),
|
||||||
|
onResize: jest.fn(),
|
||||||
|
onSetColumns: jest.fn(),
|
||||||
|
onSort: jest.fn(),
|
||||||
|
rows: esHits,
|
||||||
|
sampleSize: 30,
|
||||||
|
searchDescription: '',
|
||||||
|
searchTitle: '',
|
||||||
|
services: servicesMock,
|
||||||
|
setExpandedDoc: jest.fn(),
|
||||||
|
settings: {},
|
||||||
|
showTimeCol: true,
|
||||||
|
sort: [],
|
||||||
|
useNewFieldsApi: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComponent() {
|
||||||
|
return mountWithIntl(<DiscoverGrid {...getProps()} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedDocNr(component: ReactWrapper<DiscoverGridProps>) {
|
||||||
|
const gridSelectionBtn = findTestSubject(component, 'dscGridSelectionBtn');
|
||||||
|
if (!gridSelectionBtn.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-selected-documents');
|
||||||
|
return Number(selectedNr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayedDocNr(component: ReactWrapper<DiscoverGridProps>) {
|
||||||
|
const gridSelectionBtn = findTestSubject(component, 'discoverDocTable');
|
||||||
|
if (!gridSelectionBtn.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-document-number');
|
||||||
|
return Number(selectedNr);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleDocSelection(
|
||||||
|
component: ReactWrapper<DiscoverGridProps>,
|
||||||
|
document: ElasticSearchHit
|
||||||
|
) {
|
||||||
|
act(() => {
|
||||||
|
const docId = getDocId(document);
|
||||||
|
findTestSubject(component, `dscGridSelectDoc-${docId}`).simulate('change');
|
||||||
|
});
|
||||||
|
component.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DiscoverGrid', () => {
|
||||||
|
describe('Document selection', () => {
|
||||||
|
let component: ReactWrapper<DiscoverGridProps>;
|
||||||
|
beforeEach(() => {
|
||||||
|
component = getComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no documents are selected initially', async () => {
|
||||||
|
expect(getSelectedDocNr(component)).toBe(0);
|
||||||
|
expect(getDisplayedDocNr(component)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Allows selection/deselection of multiple documents', async () => {
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
expect(getSelectedDocNr(component)).toBe(1);
|
||||||
|
await toggleDocSelection(component, esHits[1]);
|
||||||
|
expect(getSelectedDocNr(component)).toBe(2);
|
||||||
|
await toggleDocSelection(component, esHits[1]);
|
||||||
|
expect(getSelectedDocNr(component)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deselection of all selected documents', async () => {
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
await toggleDocSelection(component, esHits[1]);
|
||||||
|
expect(getSelectedDocNr(component)).toBe(2);
|
||||||
|
findTestSubject(component, 'dscGridSelectionBtn').simulate('click');
|
||||||
|
findTestSubject(component, 'dscGridClearSelectedDocuments').simulate('click');
|
||||||
|
expect(getSelectedDocNr(component)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('showing only selected documents and undo selection', async () => {
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
await toggleDocSelection(component, esHits[1]);
|
||||||
|
expect(getSelectedDocNr(component)).toBe(2);
|
||||||
|
findTestSubject(component, 'dscGridSelectionBtn').simulate('click');
|
||||||
|
findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click');
|
||||||
|
expect(getDisplayedDocNr(component)).toBe(2);
|
||||||
|
findTestSubject(component, 'dscGridSelectionBtn').simulate('click');
|
||||||
|
component.update();
|
||||||
|
findTestSubject(component, 'dscGridShowAllDocuments').simulate('click');
|
||||||
|
expect(getDisplayedDocNr(component)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('showing only selected documents and remove filter deselecting each doc manually', async () => {
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
findTestSubject(component, 'dscGridSelectionBtn').simulate('click');
|
||||||
|
findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click');
|
||||||
|
expect(getDisplayedDocNr(component)).toBe(1);
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
expect(getDisplayedDocNr(component)).toBe(5);
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
expect(getDisplayedDocNr(component)).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('copying selected documents to clipboard', async () => {
|
||||||
|
await toggleDocSelection(component, esHits[0]);
|
||||||
|
findTestSubject(component, 'dscGridSelectionBtn').simulate('click');
|
||||||
|
expect(component.find(EuiCopy).prop('textToCopy')).toMatchInlineSnapshot(
|
||||||
|
`"[{\\"_index\\":\\"i\\",\\"_id\\":\\"1\\",\\"_score\\":1,\\"_type\\":\\"_doc\\",\\"_source\\":{\\"date\\":\\"2020-20-01T12:12:12.123\\",\\"message\\":\\"test1\\",\\"bytes\\":20}}]"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -37,6 +37,7 @@ import { defaultPageSize, gridStyle, pageSizeArr, toolbarVisibility } from './co
|
||||||
import { DiscoverServices } from '../../../build_services';
|
import { DiscoverServices } from '../../../build_services';
|
||||||
import { getDisplayedColumns } from '../../helpers/columns';
|
import { getDisplayedColumns } from '../../helpers/columns';
|
||||||
import { KibanaContextProvider } from '../../../../../kibana_react/public';
|
import { KibanaContextProvider } from '../../../../../kibana_react/public';
|
||||||
|
import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_document_selection';
|
||||||
|
|
||||||
interface SortObj {
|
interface SortObj {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -158,14 +159,27 @@ export const DiscoverGrid = ({
|
||||||
sort,
|
sort,
|
||||||
useNewFieldsApi,
|
useNewFieldsApi,
|
||||||
}: DiscoverGridProps) => {
|
}: DiscoverGridProps) => {
|
||||||
|
const [selectedDocs, setSelectedDocs] = useState<string[]>([]);
|
||||||
|
const [isFilterActive, setIsFilterActive] = useState(false);
|
||||||
const displayedColumns = getDisplayedColumns(columns, indexPattern);
|
const displayedColumns = getDisplayedColumns(columns, indexPattern);
|
||||||
const defaultColumns = displayedColumns.includes('_source');
|
const defaultColumns = displayedColumns.includes('_source');
|
||||||
|
const displayedRows = useMemo(() => {
|
||||||
|
if (!rows) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!isFilterActive || selectedDocs.length === 0) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
return rows.filter((row) => {
|
||||||
|
return selectedDocs.includes(getDocId(row));
|
||||||
|
});
|
||||||
|
}, [rows, selectedDocs, isFilterActive]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination
|
* Pagination
|
||||||
*/
|
*/
|
||||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: defaultPageSize });
|
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: defaultPageSize });
|
||||||
const rowCount = useMemo(() => (rows ? rows.length : 0), [rows]);
|
const rowCount = useMemo(() => (displayedRows ? displayedRows.length : 0), [displayedRows]);
|
||||||
const pageCount = useMemo(() => Math.ceil(rowCount / pagination.pageSize), [
|
const pageCount = useMemo(() => Math.ceil(rowCount / pagination.pageSize), [
|
||||||
rowCount,
|
rowCount,
|
||||||
pagination,
|
pagination,
|
||||||
|
@ -207,11 +221,11 @@ export const DiscoverGrid = ({
|
||||||
() =>
|
() =>
|
||||||
getRenderCellValueFn(
|
getRenderCellValueFn(
|
||||||
indexPattern,
|
indexPattern,
|
||||||
rows,
|
displayedRows,
|
||||||
rows ? rows.map((hit) => indexPattern.flattenHit(hit)) : [],
|
displayedRows ? displayedRows.map((hit) => indexPattern.flattenHit(hit)) : [],
|
||||||
useNewFieldsApi
|
useNewFieldsApi
|
||||||
),
|
),
|
||||||
[rows, indexPattern, useNewFieldsApi]
|
[displayedRows, indexPattern, useNewFieldsApi]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -240,6 +254,20 @@ export const DiscoverGrid = ({
|
||||||
]);
|
]);
|
||||||
const lead = useMemo(() => getLeadControlColumns(), []);
|
const lead = useMemo(() => getLeadControlColumns(), []);
|
||||||
|
|
||||||
|
const additionalControls = useMemo(
|
||||||
|
() =>
|
||||||
|
selectedDocs.length ? (
|
||||||
|
<DiscoverGridDocumentToolbarBtn
|
||||||
|
isFilterActive={isFilterActive}
|
||||||
|
rows={rows!}
|
||||||
|
selectedDocs={selectedDocs}
|
||||||
|
setSelectedDocs={setSelectedDocs}
|
||||||
|
setIsFilterActive={setIsFilterActive}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
[selectedDocs, isFilterActive, rows, setIsFilterActive]
|
||||||
|
);
|
||||||
|
|
||||||
if (!rowCount) {
|
if (!rowCount) {
|
||||||
return (
|
return (
|
||||||
<div className="euiDataGrid__noResults">
|
<div className="euiDataGrid__noResults">
|
||||||
|
@ -257,10 +285,17 @@ export const DiscoverGrid = ({
|
||||||
value={{
|
value={{
|
||||||
expanded: expandedDoc,
|
expanded: expandedDoc,
|
||||||
setExpanded: setExpandedDoc,
|
setExpanded: setExpandedDoc,
|
||||||
rows: rows || [],
|
rows: displayedRows,
|
||||||
onFilter,
|
onFilter,
|
||||||
indexPattern,
|
indexPattern,
|
||||||
isDarkMode: services.uiSettings.get('theme:darkMode'),
|
isDarkMode: services.uiSettings.get('theme:darkMode'),
|
||||||
|
selectedDocs,
|
||||||
|
setSelectedDocs: (newSelectedDocs) => {
|
||||||
|
setSelectedDocs(newSelectedDocs);
|
||||||
|
if (isFilterActive && newSelectedDocs.length === 0) {
|
||||||
|
setIsFilterActive(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
@ -269,6 +304,7 @@ export const DiscoverGrid = ({
|
||||||
data-shared-item=""
|
data-shared-item=""
|
||||||
data-title={searchTitle}
|
data-title={searchTitle}
|
||||||
data-description={searchDescription}
|
data-description={searchDescription}
|
||||||
|
data-document-number={displayedRows.length}
|
||||||
>
|
>
|
||||||
<KibanaContextProvider services={{ uiSettings: services.uiSettings }}>
|
<KibanaContextProvider services={{ uiSettings: services.uiSettings }}>
|
||||||
<EuiDataGridMemoized
|
<EuiDataGridMemoized
|
||||||
|
@ -294,8 +330,12 @@ export const DiscoverGrid = ({
|
||||||
? {
|
? {
|
||||||
...toolbarVisibility,
|
...toolbarVisibility,
|
||||||
showColumnSelector: false,
|
showColumnSelector: false,
|
||||||
|
additionalControls,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...toolbarVisibility,
|
||||||
|
additionalControls,
|
||||||
}
|
}
|
||||||
: toolbarVisibility
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
|
@ -335,7 +375,7 @@ export const DiscoverGrid = ({
|
||||||
<DiscoverGridFlyout
|
<DiscoverGridFlyout
|
||||||
indexPattern={indexPattern}
|
indexPattern={indexPattern}
|
||||||
hit={expandedDoc}
|
hit={expandedDoc}
|
||||||
hits={rows}
|
hits={displayedRows}
|
||||||
// if default columns are used, dont make them part of the URL - the context state handling will take care to restore them
|
// 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}
|
columns={defaultColumns ? [] : displayedColumns}
|
||||||
onFilter={onFilter}
|
onFilter={onFilter}
|
||||||
|
|
|
@ -25,6 +25,8 @@ describe('Discover cell actions ', function () {
|
||||||
onFilter: jest.fn(),
|
onFilter: jest.fn(),
|
||||||
indexPattern: indexPatternMock,
|
indexPattern: indexPatternMock,
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
|
@ -50,6 +52,8 @@ describe('Discover cell actions ', function () {
|
||||||
onFilter: jest.fn(),
|
onFilter: jest.fn(),
|
||||||
indexPattern: indexPatternMock,
|
indexPattern: indexPatternMock,
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { DiscoverGridSettings } from './types';
|
||||||
import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns';
|
import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns';
|
||||||
import { buildCellActions } from './discover_grid_cell_actions';
|
import { buildCellActions } from './discover_grid_cell_actions';
|
||||||
import { getSchemaByKbnType } from './discover_grid_schema';
|
import { getSchemaByKbnType } from './discover_grid_schema';
|
||||||
|
import { SelectButton } from './discover_grid_document_selection';
|
||||||
|
|
||||||
export function getLeadControlColumns() {
|
export function getLeadControlColumns() {
|
||||||
return [
|
return [
|
||||||
|
@ -31,6 +32,20 @@ export function getLeadControlColumns() {
|
||||||
),
|
),
|
||||||
rowCellRender: ExpandButton,
|
rowCellRender: ExpandButton,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
width: 32,
|
||||||
|
rowCellRender: SelectButton,
|
||||||
|
headerCellRender: () => (
|
||||||
|
<EuiScreenReaderOnly>
|
||||||
|
<span>
|
||||||
|
{i18n.translate('discover.selectColumnHeader', {
|
||||||
|
defaultMessage: 'Select column',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</EuiScreenReaderOnly>
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ export interface GridContext {
|
||||||
onFilter: DocViewFilterFn;
|
onFilter: DocViewFilterFn;
|
||||||
indexPattern: IndexPattern;
|
indexPattern: IndexPattern;
|
||||||
isDarkMode: boolean;
|
isDarkMode: boolean;
|
||||||
|
selectedDocs: string[];
|
||||||
|
setSelectedDocs: (selected: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultContext = ({} as unknown) as GridContext;
|
const defaultContext = ({} as unknown) as GridContext;
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { mountWithIntl } from '@kbn/test/jest';
|
||||||
|
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||||
|
import {
|
||||||
|
DiscoverGridDocumentToolbarBtn,
|
||||||
|
getDocId,
|
||||||
|
SelectButton,
|
||||||
|
} from './discover_grid_document_selection';
|
||||||
|
import { esHits } from '../../../__mocks__/es_hits';
|
||||||
|
import { indexPatternMock } from '../../../__mocks__/index_pattern';
|
||||||
|
import { DiscoverGridContext } from './discover_grid_context';
|
||||||
|
|
||||||
|
describe('document selection', () => {
|
||||||
|
describe('getDocId', () => {
|
||||||
|
test('doc with custom routing', () => {
|
||||||
|
const doc = {
|
||||||
|
_id: 'test-id',
|
||||||
|
_index: 'test-indices',
|
||||||
|
_routing: 'why-not',
|
||||||
|
};
|
||||||
|
expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::why-not"`);
|
||||||
|
});
|
||||||
|
test('doc without custom routing', () => {
|
||||||
|
const doc = {
|
||||||
|
_id: 'test-id',
|
||||||
|
_index: 'test-indices',
|
||||||
|
};
|
||||||
|
expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::"`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SelectButton', () => {
|
||||||
|
test('is not checked', () => {
|
||||||
|
const contextMock = {
|
||||||
|
expanded: undefined,
|
||||||
|
setExpanded: jest.fn(),
|
||||||
|
rows: esHits,
|
||||||
|
onFilter: jest.fn(),
|
||||||
|
indexPattern: indexPatternMock,
|
||||||
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<DiscoverGridContext.Provider value={contextMock}>
|
||||||
|
<SelectButton rowIndex={0} />
|
||||||
|
</DiscoverGridContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
|
||||||
|
expect(checkBox.props().checked).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('is checked', () => {
|
||||||
|
const contextMock = {
|
||||||
|
expanded: undefined,
|
||||||
|
setExpanded: jest.fn(),
|
||||||
|
rows: esHits,
|
||||||
|
onFilter: jest.fn(),
|
||||||
|
indexPattern: indexPatternMock,
|
||||||
|
isDarkMode: false,
|
||||||
|
selectedDocs: ['i::1::'],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<DiscoverGridContext.Provider value={contextMock}>
|
||||||
|
<SelectButton rowIndex={0} />
|
||||||
|
</DiscoverGridContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
|
||||||
|
expect(checkBox.props().checked).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adding a selection', () => {
|
||||||
|
const contextMock = {
|
||||||
|
expanded: undefined,
|
||||||
|
setExpanded: jest.fn(),
|
||||||
|
rows: esHits,
|
||||||
|
onFilter: jest.fn(),
|
||||||
|
indexPattern: indexPatternMock,
|
||||||
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<DiscoverGridContext.Provider value={contextMock}>
|
||||||
|
<SelectButton rowIndex={0} />
|
||||||
|
</DiscoverGridContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
|
||||||
|
checkBox.simulate('change');
|
||||||
|
expect(contextMock.setSelectedDocs).toHaveBeenCalledWith(['i::1::']);
|
||||||
|
});
|
||||||
|
test('removing a selection', () => {
|
||||||
|
const contextMock = {
|
||||||
|
expanded: undefined,
|
||||||
|
setExpanded: jest.fn(),
|
||||||
|
rows: esHits,
|
||||||
|
onFilter: jest.fn(),
|
||||||
|
indexPattern: indexPatternMock,
|
||||||
|
isDarkMode: false,
|
||||||
|
selectedDocs: ['i::1::'],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const component = mountWithIntl(
|
||||||
|
<DiscoverGridContext.Provider value={contextMock}>
|
||||||
|
<SelectButton rowIndex={0} />
|
||||||
|
</DiscoverGridContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::');
|
||||||
|
checkBox.simulate('change');
|
||||||
|
expect(contextMock.setSelectedDocs).toHaveBeenCalledWith([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('DiscoverGridDocumentToolbarBtn', () => {
|
||||||
|
test('it renders a button clickable button', () => {
|
||||||
|
const props = {
|
||||||
|
isFilterActive: false,
|
||||||
|
rows: esHits,
|
||||||
|
selectedDocs: ['i::1::'],
|
||||||
|
setIsFilterActive: jest.fn(),
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
|
};
|
||||||
|
const component = mountWithIntl(<DiscoverGridDocumentToolbarBtn {...props} />);
|
||||||
|
const button = findTestSubject(component, 'dscGridSelectionBtn');
|
||||||
|
expect(button.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
import React, { useCallback, useState, useContext, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
EuiButtonEmpty,
|
||||||
|
EuiContextMenuItem,
|
||||||
|
EuiContextMenuPanel,
|
||||||
|
EuiCopy,
|
||||||
|
EuiPopover,
|
||||||
|
EuiCheckbox,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ElasticSearchHit } from '../../doc_views/doc_views_types';
|
||||||
|
import { DiscoverGridContext } from './discover_grid_context';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returning a generated id of a given ES document, since `_id` can be the same
|
||||||
|
* when using different indices and shard routing
|
||||||
|
*/
|
||||||
|
export const getDocId = (doc: ElasticSearchHit & { _routing?: string }) => {
|
||||||
|
const routing = doc._routing ? doc._routing : '';
|
||||||
|
return [doc._index, doc._id, routing].join('::');
|
||||||
|
};
|
||||||
|
export const SelectButton = ({ rowIndex }: { rowIndex: number }) => {
|
||||||
|
const ctx = useContext(DiscoverGridContext);
|
||||||
|
const doc = useMemo(() => ctx.rows[rowIndex], [ctx.rows, rowIndex]);
|
||||||
|
const id = useMemo(() => getDocId(doc), [doc]);
|
||||||
|
const checked = useMemo(() => ctx.selectedDocs.includes(id), [ctx.selectedDocs, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiCheckbox
|
||||||
|
id={id}
|
||||||
|
label=""
|
||||||
|
checked={checked}
|
||||||
|
data-test-subj={`dscGridSelectDoc-${id}`}
|
||||||
|
onChange={() => {
|
||||||
|
if (checked) {
|
||||||
|
const newSelection = ctx.selectedDocs.filter((docId) => docId !== id);
|
||||||
|
ctx.setSelectedDocs(newSelection);
|
||||||
|
} else {
|
||||||
|
ctx.setSelectedDocs([...ctx.selectedDocs, id]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DiscoverGridDocumentToolbarBtn({
|
||||||
|
isFilterActive,
|
||||||
|
rows,
|
||||||
|
selectedDocs,
|
||||||
|
setIsFilterActive,
|
||||||
|
setSelectedDocs,
|
||||||
|
}: {
|
||||||
|
isFilterActive: boolean;
|
||||||
|
rows: ElasticSearchHit[];
|
||||||
|
selectedDocs: string[];
|
||||||
|
setIsFilterActive: (value: boolean) => void;
|
||||||
|
setSelectedDocs: (value: string[]) => void;
|
||||||
|
}) {
|
||||||
|
const [isSelectionPopoverOpen, setIsSelectionPopoverOpen] = useState(false);
|
||||||
|
|
||||||
|
const getMenuItems = useCallback(() => {
|
||||||
|
return [
|
||||||
|
isFilterActive ? (
|
||||||
|
<EuiContextMenuItem
|
||||||
|
data-test-subj="dscGridShowAllDocuments"
|
||||||
|
key="showAllDocuments"
|
||||||
|
icon="eye"
|
||||||
|
onClick={() => {
|
||||||
|
setIsSelectionPopoverOpen(false);
|
||||||
|
setIsFilterActive(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="discover.showAllDocuments" defaultMessage="Show all documents" />
|
||||||
|
</EuiContextMenuItem>
|
||||||
|
) : (
|
||||||
|
<EuiContextMenuItem
|
||||||
|
data-test-subj="dscGridShowSelectedDocuments"
|
||||||
|
key="showSelectedDocuments"
|
||||||
|
icon="eye"
|
||||||
|
onClick={() => {
|
||||||
|
setIsSelectionPopoverOpen(false);
|
||||||
|
setIsFilterActive(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="discover.showSelectedDocumentsOnly"
|
||||||
|
defaultMessage="Show selected documents only"
|
||||||
|
/>
|
||||||
|
</EuiContextMenuItem>
|
||||||
|
),
|
||||||
|
|
||||||
|
<EuiContextMenuItem
|
||||||
|
data-test-subj="dscGridClearSelectedDocuments"
|
||||||
|
key="clearSelection"
|
||||||
|
icon="cross"
|
||||||
|
onClick={() => {
|
||||||
|
setIsSelectionPopoverOpen(false);
|
||||||
|
setSelectedDocs([]);
|
||||||
|
setIsFilterActive(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="discover.clearSelection" defaultMessage="Clear selection" />
|
||||||
|
</EuiContextMenuItem>,
|
||||||
|
<EuiCopy
|
||||||
|
key="copyJsonWrapper"
|
||||||
|
data-test-subj="dscGridCopySelectedDocumentsJSON"
|
||||||
|
textToCopy={
|
||||||
|
rows ? JSON.stringify(rows.filter((row) => selectedDocs.includes(getDocId(row)))) : ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(copy) => (
|
||||||
|
<EuiContextMenuItem key="copyJSON" icon="copyClipboard" onClick={copy}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="discover.copyToClipboardJSON"
|
||||||
|
defaultMessage="Copy documents to clipboard (JSON)"
|
||||||
|
/>
|
||||||
|
</EuiContextMenuItem>
|
||||||
|
)}
|
||||||
|
</EuiCopy>,
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
isFilterActive,
|
||||||
|
rows,
|
||||||
|
selectedDocs,
|
||||||
|
setIsFilterActive,
|
||||||
|
setIsSelectionPopoverOpen,
|
||||||
|
setSelectedDocs,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiPopover
|
||||||
|
closePopover={() => setIsSelectionPopoverOpen(false)}
|
||||||
|
isOpen={isSelectionPopoverOpen}
|
||||||
|
panelPaddingSize="none"
|
||||||
|
button={
|
||||||
|
<EuiButtonEmpty
|
||||||
|
size="xs"
|
||||||
|
color="text"
|
||||||
|
iconType="documents"
|
||||||
|
onClick={() => setIsSelectionPopoverOpen(true)}
|
||||||
|
data-selected-documents={selectedDocs.length}
|
||||||
|
data-test-subj="dscGridSelectionBtn"
|
||||||
|
isSelected={isFilterActive}
|
||||||
|
className={classNames({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
euiDataGrid__controlBtn: true,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'euiDataGrid__controlBtn--active': isFilterActive,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="discover.selectedDocumentsNumber"
|
||||||
|
defaultMessage="{nr} documents selected"
|
||||||
|
values={{ nr: selectedDocs.length }}
|
||||||
|
/>
|
||||||
|
</EuiButtonEmpty>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isSelectionPopoverOpen && <EuiContextMenuPanel items={getMenuItems()} />}
|
||||||
|
</EuiPopover>
|
||||||
|
);
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ describe('Discover grid view button ', function () {
|
||||||
onFilter: jest.fn(),
|
onFilter: jest.fn(),
|
||||||
indexPattern: indexPatternMock,
|
indexPattern: indexPatternMock,
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
|
@ -49,6 +51,8 @@ describe('Discover grid view button ', function () {
|
||||||
onFilter: jest.fn(),
|
onFilter: jest.fn(),
|
||||||
indexPattern: indexPatternMock,
|
indexPattern: indexPatternMock,
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
|
@ -75,6 +79,8 @@ describe('Discover grid view button ', function () {
|
||||||
onFilter: jest.fn(),
|
onFilter: jest.fn(),
|
||||||
indexPattern: indexPatternMock,
|
indexPattern: indexPatternMock,
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
|
selectedDocs: [],
|
||||||
|
setSelectedDocs: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
|
|
|
@ -47,12 +47,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('are added when a cell filter is clicked', async function () {
|
it('are added when a cell filter is clicked', async function () {
|
||||||
await find.clickByCssSelector(`[role="gridcell"]:nth-child(3)`);
|
await find.clickByCssSelector(`[role="gridcell"]:nth-child(4)`);
|
||||||
// needs a short delay between becoming visible & being clickable
|
// needs a short delay between becoming visible & being clickable
|
||||||
await PageObjects.common.sleep(250);
|
await PageObjects.common.sleep(250);
|
||||||
await find.clickByCssSelector(`[data-test-subj="filterOutButton"]`);
|
await find.clickByCssSelector(`[data-test-subj="filterOutButton"]`);
|
||||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
await find.clickByCssSelector(`[role="gridcell"]:nth-child(3)`);
|
await find.clickByCssSelector(`[role="gridcell"]:nth-child(4)`);
|
||||||
await PageObjects.common.sleep(250);
|
await PageObjects.common.sleep(250);
|
||||||
await find.clickByCssSelector(`[data-test-subj="filterForButton"]`);
|
await find.clickByCssSelector(`[data-test-subj="filterForButton"]`);
|
||||||
const filterCount = await filterBar.getFilterCount();
|
const filterCount = await filterBar.getFilterCount();
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||||
|
|
||||||
await retry.waitFor('first cell contains expected timestamp', async () => {
|
await retry.waitFor('first cell contains expected timestamp', async () => {
|
||||||
const cell = await dataGrid.getCellElement(1, 2);
|
const cell = await dataGrid.getCellElement(1, 3);
|
||||||
const text = await cell.getVisibleText();
|
const text = await cell.getVisibleText();
|
||||||
return text === expectedTimeStamp;
|
return text === expectedTimeStamp;
|
||||||
});
|
});
|
||||||
|
|
|
@ -168,7 +168,7 @@ export function DataGridProvider({ getService, getPageObjects }: FtrProviderCont
|
||||||
const textArr = [];
|
const textArr = [];
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
for (const cell of result) {
|
for (const cell of result) {
|
||||||
if (idx > 0) {
|
if (idx > 1) {
|
||||||
textArr.push(await cell.getVisibleText());
|
textArr.push(await cell.getVisibleText());
|
||||||
}
|
}
|
||||||
idx++;
|
idx++;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue