diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index e137955674457..96888a07be68f 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -10,13 +10,16 @@ import { DiscoverServices } from '../build_services'; import { dataPluginMock } from '../../../data/public/mocks'; import { chromeServiceMock, coreMock, docLinksServiceMock } from '../../../../core/public/mocks'; import { + CONTEXT_STEP_SETTING, DEFAULT_COLUMNS_SETTING, + DOC_HIDE_TIME_COLUMN_SETTING, SAMPLE_SIZE_SETTING, SORT_DEFAULT_ORDER_SETTING, } from '../../common'; import { savedSearchMock } from './saved_search'; import { UI_SETTINGS } from '../../../data/common'; import { TopNavMenu } from '../../../navigation/public'; +import { FORMATS_UI_SETTINGS } from 'src/plugins/field_formats/common'; const dataPlugin = dataPluginMock.createStartContract(); export const discoverServiceMock = ({ @@ -49,10 +52,16 @@ export const discoverServiceMock = ({ return []; } else if (key === UI_SETTINGS.META_FIELDS) { return []; - } else if (key === SAMPLE_SIZE_SETTING) { - return 250; + } else if (key === DOC_HIDE_TIME_COLUMN_SETTING) { + return false; + } else if (key === CONTEXT_STEP_SETTING) { + return 5; } else if (key === SORT_DEFAULT_ORDER_SETTING) { return 'desc'; + } else if (key === FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE) { + return false; + } else if (key === SAMPLE_SIZE_SETTING) { + return 250; } }, isDefault: (key: string) => { diff --git a/src/plugins/discover/public/application/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts index 727f941f5fee3..e9da3a7c4784f 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -22,10 +22,7 @@ export enum SurrDocType { } export type EsHitRecord = Required< - Pick< - estypes.SearchResponse['hits']['hits'][number], - '_id' | 'fields' | 'sort' | '_index' | '_version' - > + Pick > & { _source?: Record; _score?: number; diff --git a/src/plugins/discover/public/application/angular/create_discover_grid_directive.tsx b/src/plugins/discover/public/application/angular/create_discover_grid_directive.tsx index 810be94ce24b0..b79936bd6f385 100644 --- a/src/plugins/discover/public/application/angular/create_discover_grid_directive.tsx +++ b/src/plugins/discover/public/application/angular/create_discover_grid_directive.tsx @@ -7,25 +7,40 @@ */ import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { DiscoverGrid, DiscoverGridProps } from '../components/discover_grid/discover_grid'; import { getServices } from '../../kibana_services'; import { ElasticSearchHit } from '../doc_views/doc_views_types'; +import { TotalDocuments } from '../apps/main/components/total_documents/total_documents'; + +export interface DiscoverGridEmbeddableProps extends DiscoverGridProps { + totalHitCount: number; +} export const DataGridMemoized = React.memo((props: DiscoverGridProps) => ( )); -export function DiscoverGridEmbeddable(props: DiscoverGridProps) { +export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { const [expandedDoc, setExpandedDoc] = useState(undefined); return ( - + + {props.totalHitCount !== 0 && ( + + + + )} + + + + ); } diff --git a/src/plugins/discover/public/application/angular/directives/render_complete.ts b/src/plugins/discover/public/application/angular/directives/render_complete.ts deleted file mode 100644 index b62820bf876d8..0000000000000 --- a/src/plugins/discover/public/application/angular/directives/render_complete.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { IScope } from 'angular'; -import { RenderCompleteListener } from '../../../../../kibana_utils/public'; - -export function createRenderCompleteDirective() { - return { - controller($scope: IScope, $element: JQLite) { - const el = $element[0]; - const renderCompleteListener = new RenderCompleteListener(el); - $scope.$on('$destroy', renderCompleteListener.destroy); - }, - }; -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap deleted file mode 100644 index fd0c1b4b2af8d..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`it renders ToolBarPagerButtons 1`] = ` - - - - - - - - -`; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap deleted file mode 100644 index 96d2994bbe68f..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap +++ /dev/null @@ -1,10 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`it renders ToolBarPagerText without crashing 1`] = ` -
- 1–2 of 3 -
-`; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts b/src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts deleted file mode 100644 index 180da83beb2af..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { ToolBarPagerText } from './tool_bar_pager_text'; -import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function createToolBarPagerTextDirective(reactDirective: any) { - return reactDirective(ToolBarPagerText); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function createToolBarPagerButtonsDirective(reactDirective: any) { - return reactDirective(ToolBarPagerButtons); -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx deleted file mode 100644 index 061dae2d50658..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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, shallowWithIntl } from '@kbn/test/jest'; -import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; -import { findTestSubject } from '@elastic/eui/lib/test'; - -test('it renders ToolBarPagerButtons', () => { - const props = { - hasPreviousPage: true, - hasNextPage: true, - onPageNext: jest.fn(), - onPagePrevious: jest.fn(), - }; - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); -}); - -test('it renders ToolBarPagerButtons with clickable next and previous button', () => { - const props = { - hasPreviousPage: true, - hasNextPage: true, - onPageNext: jest.fn(), - onPagePrevious: jest.fn(), - }; - const wrapper = mountWithIntl(); - findTestSubject(wrapper, 'btnPrevPage').simulate('click'); - expect(props.onPagePrevious).toHaveBeenCalledTimes(1); - findTestSubject(wrapper, 'btnNextPage').simulate('click'); - expect(props.onPageNext).toHaveBeenCalledTimes(1); -}); - -test('it renders ToolBarPagerButtons with disabled next and previous button', () => { - const props = { - hasPreviousPage: false, - hasNextPage: false, - onPageNext: jest.fn(), - onPagePrevious: jest.fn(), - }; - const wrapper = mountWithIntl(); - findTestSubject(wrapper, 'btnPrevPage').simulate('click'); - expect(props.onPagePrevious).toHaveBeenCalledTimes(0); - findTestSubject(wrapper, 'btnNextPage').simulate('click'); - expect(props.onPageNext).toHaveBeenCalledTimes(0); -}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx deleted file mode 100644 index d825220163165..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -interface Props { - hasPreviousPage: boolean; - hasNextPage: boolean; - onPageNext: () => void; - onPagePrevious: () => void; -} - -export function ToolBarPagerButtons(props: Props) { - return ( - - - props.onPagePrevious()} - isDisabled={!props.hasPreviousPage} - data-test-subj="btnPrevPage" - aria-label={i18n.translate( - 'discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel', - { - defaultMessage: 'Previous page in table', - } - )} - /> - - - props.onPageNext()} - isDisabled={!props.hasNextPage} - data-test-subj="btnNextPage" - aria-label={i18n.translate( - 'discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel', - { - defaultMessage: 'Next page in table', - } - )} - /> - - - ); -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.scss b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.scss deleted file mode 100644 index 446e852f51e05..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.scss +++ /dev/null @@ -1,5 +0,0 @@ -.kbnDocTable__toolBarText { - line-height: $euiLineHeight; - color: #69707D; - white-space: nowrap; -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx deleted file mode 100644 index a8ebfcc9bb311..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { renderWithIntl } from '@kbn/test/jest'; -import { ToolBarPagerText } from './tool_bar_pager_text'; - -test('it renders ToolBarPagerText without crashing', () => { - const props = { - startItem: 1, - endItem: 2, - totalItems: 3, - }; - const wrapper = renderWithIntl(); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx deleted file mode 100644 index 5db68952b69ca..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 './tool_bar_pager_text.scss'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; - -interface Props { - startItem: number; - endItem: number; - totalItems: number; -} - -export function ToolBarPagerText({ startItem, endItem, totalItems }: Props) { - return ( - -
- -
-
- ); -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js deleted file mode 100644 index 1a3b34c45d05e..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js +++ /dev/null @@ -1,474 +0,0 @@ -/* - * 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 angular from 'angular'; -import 'angular-mocks'; -import 'angular-sanitize'; -import 'angular-route'; -import _ from 'lodash'; -import sinon from 'sinon'; -import { getFakeRow } from '../../../../__fixtures__/fake_row'; -import $ from 'jquery'; -import FixturesStubbedLogstashIndexPatternProvider from '../../../../__fixtures__/stubbed_logstash_index_pattern'; -import { setScopedHistory, setServices, setDocViewsRegistry } from '../../../../kibana_services'; -import { coreMock } from '../../../../../../../core/public/mocks'; -import { dataPluginMock } from '../../../../../../data/public/mocks'; -import { navigationPluginMock } from '../../../../../../navigation/public/mocks'; -import { initAngularBootstrap } from '../../../../../../kibana_legacy/public/angular_bootstrap'; -import { getInnerAngularModule } from '../../get_inner_angular'; -import { createBrowserHistory } from 'history'; - -const fakeRowVals = { - time: 'time_formatted', - bytes: 'bytes_formatted', - '@timestamp': '@timestamp_formatted', - request_body: 'request_body_formatted', -}; - -describe('Doc Table', () => { - const core = coreMock.createStart(); - const dataMock = dataPluginMock.createStartContract(); - let $parentScope; - let $scope; - let $elementScope; - let timeout; - let registry = []; - - // Stub out a minimal mapping of 4 fields - let mapping; - - beforeAll(async () => { - await initAngularBootstrap(); - }); - beforeAll(() => setScopedHistory(createBrowserHistory())); - beforeEach(() => { - angular.element.prototype.slice = jest.fn(function (index) { - return $(this).slice(index); - }); - angular.element.prototype.filter = jest.fn(function (condition) { - return $(this).filter(condition); - }); - angular.element.prototype.toggle = jest.fn(function (name) { - return $(this).toggle(name); - }); - angular.element.prototype.is = jest.fn(function (name) { - return $(this).is(name); - }); - setServices({ - uiSettings: core.uiSettings, - filterManager: dataMock.query.filterManager, - addBasePath: (path) => path, - }); - - setDocViewsRegistry({ - addDocView(view) { - registry.push(view); - }, - getDocViewsSorted() { - return registry; - }, - resetRegistry: () => { - registry = []; - }, - }); - - getInnerAngularModule( - 'app/discover', - core, - { - data: dataMock, - navigation: navigationPluginMock.createStartContract(), - }, - coreMock.createPluginInitializerContext() - ); - angular.mock.module('app/discover'); - }); - beforeEach( - angular.mock.inject(function ($rootScope, Private, $timeout) { - $parentScope = $rootScope; - timeout = $timeout; - $parentScope.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - mapping = $parentScope.indexPattern.fields; - - // Stub `getConverterFor` for a field in the indexPattern to return mock data. - - const convertFn = (value, type, options) => { - const fieldName = _.get(options, 'field.name', null); - return fakeRowVals[fieldName] || ''; - }; - $parentScope.indexPattern.getFormatterForField = () => ({ - convert: convertFn, - getConverterFor: () => convertFn, - }); - }) - ); - - afterEach(() => { - delete angular.element.prototype.slice; - delete angular.element.prototype.filter; - delete angular.element.prototype.toggle; - delete angular.element.prototype.is; - }); - - // Sets up the directive, take an element, and a list of properties to attach to the parent scope. - const init = function ($elem, props) { - angular.mock.inject(function ($compile) { - _.assign($parentScope, props); - const el = $compile($elem)($parentScope); - $elementScope = el.scope(); - el.scope().$digest(); - $scope = el.isolateScope(); - }); - }; - - const destroy = () => { - $scope.$destroy(); - $parentScope.$destroy(); - }; - - // For testing column removing/adding for the header and the rows - const columnTests = function (elemType, parentElem) { - test('should create a time column if the timefield is defined', () => { - const childElems = parentElem.find(elemType); - expect(childElems.length).toBe(1); - }); - - test('should be able to add and remove columns', () => { - let childElems; - - // Should include a column for toggling and the time column by default - $parentScope.columns = ['bytes']; - $elementScope.$digest(); - childElems = parentElem.find(elemType); - expect(childElems.length).toBe(2); - expect($(childElems[1]).text()).toContain('bytes'); - - $parentScope.columns = ['bytes', 'request_body']; - $elementScope.$digest(); - childElems = parentElem.find(elemType); - expect(childElems.length).toBe(3); - expect($(childElems[2]).text()).toContain('request_body'); - - $parentScope.columns = ['request_body']; - $elementScope.$digest(); - childElems = parentElem.find(elemType); - expect(childElems.length).toBe(2); - expect($(childElems[1]).text()).toContain('request_body'); - }); - - test('should create only the toggle column if there is no timeField', () => { - delete $scope.indexPattern.timeFieldName; - $scope.$digest(); - timeout.flush(); - - const childElems = parentElem.find(elemType); - expect(childElems.length).toBe(0); - }); - }; - - describe('kbnTableRow', () => { - const $elem = $( - '' - ); - let row; - - beforeEach(() => { - row = getFakeRow(0, mapping); - - init($elem, { - row, - columns: [], - sorting: [], - filter: sinon.spy(), - maxLength: 50, - }); - }); - afterEach(() => { - destroy(); - }); - - describe('adding and removing columns', () => { - columnTests('[data-test-subj~="docTableField"]', $elem); - }); - - describe('details row', () => { - test('should be an empty tr by default', () => { - expect($elem.next().is('tr')).toBe(true); - expect($elem.next().text()).toBe(''); - }); - - test('should expand the detail row when the toggle arrow is clicked', () => { - $elem.children(':first-child').click(); - expect($elem.next().text()).not.toBe(''); - }); - - describe('expanded', () => { - let $details; - beforeEach(() => { - // Open the row - $scope.toggleRow(); - timeout.flush(); - $details = $elem.next(); - }); - afterEach(() => { - // Close the row - $scope.toggleRow(); - }); - - test('should be a tr with something in it', () => { - expect($details.is('tr')).toBe(true); - expect($details.text()).toBeTruthy(); - }); - }); - }); - }); - - describe('kbnTableRow meta', () => { - const $elem = angular.element( - '' - ); - let row; - - beforeEach(() => { - row = getFakeRow(0, mapping); - - init($elem, { - row: row, - columns: [], - sorting: [], - filtering: sinon.spy(), - maxLength: 50, - }); - - // Open the row - $scope.toggleRow(); - $scope.$digest(); - timeout.flush(); - $elem.next(); - }); - - afterEach(() => { - destroy(); - }); - - /** this no longer works with the new plugin approach - test('should render even when the row source contains a field with the same name as a meta field', () => { - setTimeout(() => { - //this should be overridden by later changes - }, 100); - expect($details.find('tr').length).toBe(_.keys($parentScope.indexPattern.flattenHit($scope.row)).length); - }); */ - }); - - describe('row diffing', () => { - let $row; - let $scope; - let $root; - let $before; - - beforeEach( - angular.mock.inject(function ($rootScope, $compile, Private) { - $root = $rootScope; - $root.row = getFakeRow(0, mapping); - $root.columns = ['_source']; - $root.sorting = []; - $root.filtering = sinon.spy(); - $root.maxLength = 50; - $root.mapping = mapping; - $root.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - $row = $('').attr({ - 'kbn-table-row': 'row', - columns: 'columns', - sorting: 'sorting', - filtering: 'filtering', - 'index-pattern': 'indexPattern', - }); - - $scope = $root.$new(); - $compile($row)($scope); - $root.$apply(); - - $before = $row.find('td'); - expect($before).toHaveLength(3); - expect($before.eq(0).text().trim()).toBe(''); - expect($before.eq(1).text().trim()).toMatch(/^time_formatted/); - }) - ); - - afterEach(() => { - $row.remove(); - }); - - test('handles a new column', () => { - $root.columns.push('bytes'); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(4); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after[2].outerHTML).toBe($before[2].outerHTML); - expect($after.eq(3).text().trim()).toMatch(/^bytes_formatted/); - }); - - test('handles two new columns at once', () => { - $root.columns.push('bytes'); - $root.columns.push('request_body'); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(5); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after[2].outerHTML).toBe($before[2].outerHTML); - expect($after.eq(3).text().trim()).toMatch(/^bytes_formatted/); - expect($after.eq(4).text().trim()).toMatch(/^request_body_formatted/); - }); - - test('handles three new columns in odd places', () => { - $root.columns = ['@timestamp', 'bytes', '_source', 'request_body']; - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(6); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after.eq(2).text().trim()).toMatch(/^@timestamp_formatted/); - expect($after.eq(3).text().trim()).toMatch(/^bytes_formatted/); - expect($after[4].outerHTML).toBe($before[2].outerHTML); - expect($after.eq(5).text().trim()).toMatch(/^request_body_formatted/); - }); - - test('handles a removed column', () => { - _.pull($root.columns, '_source'); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(2); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - }); - - test('handles two removed columns', () => { - // first add a column - $root.columns.push('@timestamp'); - $root.$apply(); - - const $mid = $row.find('td'); - expect($mid).toHaveLength(4); - - $root.columns.pop(); - $root.columns.pop(); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(2); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - }); - - test('handles three removed random columns', () => { - // first add two column - $root.columns.push('@timestamp', 'bytes'); - $root.$apply(); - - const $mid = $row.find('td'); - expect($mid).toHaveLength(5); - - $root.columns[0] = false; // _source - $root.columns[2] = false; // bytes - $root.columns = $root.columns.filter(Boolean); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(3); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after.eq(2).text().trim()).toMatch(/^@timestamp_formatted/); - }); - - test('handles two columns with the same content', () => { - const tempVal = fakeRowVals.request_body; - fakeRowVals.request_body = 'bytes_formatted'; - - $root.columns.length = 0; - $root.columns.push('bytes'); - $root.columns.push('request_body'); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(4); - expect($after.eq(2).text().trim()).toMatch(/^bytes_formatted/); - expect($after.eq(3).text().trim()).toMatch(/^bytes_formatted/); - fakeRowVals.request_body = tempVal; - }); - - test('handles two columns swapping position', () => { - $root.columns.push('bytes'); - $root.$apply(); - - const $mid = $row.find('td'); - expect($mid).toHaveLength(4); - - $root.columns.reverse(); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(4); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after[2].outerHTML).toBe($mid[3].outerHTML); - expect($after[3].outerHTML).toBe($mid[2].outerHTML); - }); - - test('handles four columns all reversing position', () => { - $root.columns.push('bytes', 'response', '@timestamp'); - $root.$apply(); - - const $mid = $row.find('td'); - expect($mid).toHaveLength(6); - - $root.columns.reverse(); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(6); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after[2].outerHTML).toBe($mid[5].outerHTML); - expect($after[3].outerHTML).toBe($mid[4].outerHTML); - expect($after[4].outerHTML).toBe($mid[3].outerHTML); - expect($after[5].outerHTML).toBe($mid[2].outerHTML); - }); - - test('handles multiple columns with the same name', () => { - $root.columns.push('bytes', 'bytes', 'bytes'); - $root.$apply(); - - const $after = $row.find('td'); - expect($after).toHaveLength(6); - expect($after[0].outerHTML).toBe($before[0].outerHTML); - expect($after[1].outerHTML).toBe($before[1].outerHTML); - expect($after[2].outerHTML).toBe($before[2].outerHTML); - expect($after.eq(3).text().trim()).toMatch(/^bytes_formatted/); - expect($after.eq(4).text().trim()).toMatch(/^bytes_formatted/); - expect($after.eq(5).text().trim()).toMatch(/^bytes_formatted/); - }); - }); -}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts deleted file mode 100644 index 0f6c86df0db64..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { TableHeader } from './table_header/table_header'; -import { getServices } from '../../../../kibana_services'; -import { SORT_DEFAULT_ORDER_SETTING, DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../../common'; -import { FORMATS_UI_SETTINGS } from '../../../../../../field_formats/common'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function createTableHeaderDirective(reactDirective: any) { - const { uiSettings: config } = getServices(); - - return reactDirective( - TableHeader, - [ - ['columns', { watchDepth: 'collection' }], - ['hideTimeColumn', { watchDepth: 'value' }], - ['indexPattern', { watchDepth: 'reference' }], - ['isShortDots', { watchDepth: 'value' }], - ['onChangeSortOrder', { watchDepth: 'reference' }], - ['onMoveColumn', { watchDepth: 'reference' }], - ['onRemoveColumn', { watchDepth: 'reference' }], - ['sortOrder', { watchDepth: 'collection' }], - ], - { restrict: 'A' }, - { - hideTimeColumn: config.get(DOC_HIDE_TIME_COLUMN_SETTING, false), - isShortDots: config.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE), - defaultSortOrder: config.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), - } - ); -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts deleted file mode 100644 index 1d6956fc80920..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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 { find } from 'lodash'; -import $ from 'jquery'; -import openRowHtml from './table_row/open.html'; -import detailsHtml from './table_row/details.html'; -import { dispatchRenderComplete } from '../../../../../../kibana_utils/public'; -import { DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../../common'; -import { getServices } from '../../../../kibana_services'; -import { getContextUrl } from '../../../helpers/get_context_url'; -import { formatRow, formatTopLevelObject } from '../../helpers'; -import { truncateByHeight } from './table_row/truncate_by_height'; -import { cell } from './table_row/cell'; - -// guesstimate at the minimum number of chars wide cells in the table should be -const MIN_LINE_LENGTH = 20; - -interface LazyScope extends ng.IScope { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -} - -export function createTableRowDirective($compile: ng.ICompileService) { - return { - restrict: 'A', - scope: { - columns: '=', - filter: '=', - indexPattern: '=', - row: '=kbnTableRow', - onAddColumn: '=?', - onRemoveColumn: '=?', - useNewFieldsApi: '<', - }, - link: ($scope: LazyScope, $el: JQuery) => { - $el.after(''); - $el.empty(); - - // when we compile the details, we use this $scope - let $detailsScope: LazyScope; - - // when we compile the toggle button in the summary, we use this $scope - let $toggleScope; - - // toggle display of the rows details, a full list of the fields from each row - $scope.toggleRow = () => { - const $detailsTr = $el.next(); - - $scope.open = !$scope.open; - - /// - // add/remove $details children - /// - - $detailsTr.toggle($scope.open); - - if (!$scope.open) { - // close the child scope if it exists - $detailsScope.$destroy(); - // no need to go any further - return; - } else { - $detailsScope = $scope.$new(); - } - - // empty the details and rebuild it - $detailsTr.html(detailsHtml); - $detailsScope.row = $scope.row; - $detailsScope.hit = $scope.row; - $detailsScope.uriEncodedId = encodeURIComponent($detailsScope.hit._id); - - $compile($detailsTr)($detailsScope); - }; - - $scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight', '[]columns'], () => { - createSummaryRow($scope.row); - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - $scope.inlineFilter = function inlineFilter($event: any, type: string) { - const column = $($event.currentTarget).data().column; - const field = $scope.indexPattern.fields.getByName(column); - $scope.filter(field, $scope.flattenedRow[column], type); - }; - - $scope.getContextAppHref = () => { - return getContextUrl( - $scope.row._id, - $scope.indexPattern.id, - $scope.columns, - getServices().filterManager, - getServices().addBasePath - ); - }; - - $scope.getSingleDocHref = () => { - return getServices().addBasePath( - `/app/discover#/doc/${$scope.indexPattern.id}/${ - $scope.row._index - }?id=${encodeURIComponent($scope.row._id)}` - ); - }; - - // create a tr element that lists the value for each *column* - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function createSummaryRow(row: any) { - const indexPattern = $scope.indexPattern; - $scope.flattenedRow = indexPattern.flattenHit(row); - - // We just create a string here because its faster. - const newHtmls = [openRowHtml]; - - const mapping = indexPattern.fields.getByName; - const hideTimeColumn = getServices().uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false); - if (indexPattern.timeFieldName && !hideTimeColumn) { - newHtmls.push( - cell({ - timefield: true, - formatted: _displayField(row, indexPattern.timeFieldName), - filterable: mapping(indexPattern.timeFieldName).filterable && $scope.filter, - column: indexPattern.timeFieldName, - }) - ); - } - - if ($scope.columns.length === 0 && $scope.useNewFieldsApi) { - const formatted = formatRow(row, indexPattern); - - newHtmls.push( - cell({ - timefield: false, - sourcefield: true, - formatted, - filterable: false, - column: '__document__', - }) - ); - } else { - $scope.columns.forEach(function (column: string) { - const isFilterable = mapping(column) && mapping(column).filterable && $scope.filter; - if ($scope.useNewFieldsApi && !mapping(column) && !row.fields[column]) { - const innerColumns = Object.fromEntries( - Object.entries(row.fields).filter(([key]) => { - return key.indexOf(`${column}.`) === 0; - }) - ); - newHtmls.push( - cell({ - timefield: false, - sourcefield: true, - formatted: formatTopLevelObject(row, innerColumns, indexPattern), - filterable: false, - column, - }) - ); - } else { - newHtmls.push( - cell({ - timefield: false, - sourcefield: column === '_source', - formatted: _displayField(row, column, true), - filterable: isFilterable, - column, - }) - ); - } - }); - } - - let $cells = $el.children(); - newHtmls.forEach(function (html, i) { - const $cell = $cells.eq(i); - if ($cell.data('discover:html') === html) return; - - const reuse = find($cells.slice(i + 1), (c) => { - return $.data(c, 'discover:html') === html; - }); - - const $target = reuse ? $(reuse).detach() : $(html); - $target.data('discover:html', html); - const $before = $cells.eq(i - 1); - if ($before.length) { - $before.after($target); - } else { - $el.append($target); - } - - // rebuild cells since we modified the children - $cells = $el.children(); - - if (!reuse) { - $toggleScope = $scope.$new(); - $compile($target)($toggleScope); - } - }); - - if ($scope.open) { - $detailsScope.row = row; - } - - // trim off cells that were not used rest of the cells - $cells.filter(':gt(' + (newHtmls.length - 1) + ')').remove(); - dispatchRenderComplete($el[0]); - } - - /** - * Fill an element with the value of a field - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function _displayField(row: any, fieldName: string, truncate = false) { - const indexPattern = $scope.indexPattern; - const text = indexPattern.formatField(row, fieldName); - - if (truncate && text.length > MIN_LINE_LENGTH) { - return truncateByHeight({ - body: text, - }); - } - - return text; - } - }, - }; -} diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.test.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.test.ts deleted file mode 100644 index c6d0d324b9bc2..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 { cell } from './cell'; - -describe('cell renderer', () => { - it('renders a cell without filter buttons if it is not filterable', () => { - expect( - cell({ - filterable: false, - column: 'foo', - timefield: true, - sourcefield: false, - formatted: 'formatted content', - }) - ).toMatchInlineSnapshot(` - "formatted content - " - `); - }); - - it('renders a cell with filter buttons if it is filterable', () => { - expect( - cell({ - filterable: true, - column: 'foo', - timefield: true, - sourcefield: false, - formatted: 'formatted content', - }) - ).toMatchInlineSnapshot(` - "formatted content - " - `); - }); - - it('renders a sourcefield', () => { - expect( - cell({ - filterable: false, - column: 'foo', - timefield: false, - sourcefield: true, - formatted: 'formatted content', - }) - ).toMatchInlineSnapshot(` - "formatted content - " - `); - }); - - it('renders a field that is neither a timefield or sourcefield', () => { - expect( - cell({ - filterable: false, - column: 'foo', - timefield: false, - sourcefield: false, - formatted: 'formatted content', - }) - ).toMatchInlineSnapshot(` - "formatted content - " - `); - }); - - it('renders the "formatted" contents without any manipulation', () => { - expect( - cell({ - filterable: false, - column: 'foo', - timefield: true, - sourcefield: false, - formatted: - '
 hey you can put HTML & stuff in here 
', - }) - ).toMatchInlineSnapshot(` - "
 hey you can put HTML & stuff in here 
- " - `); - }); - - it('escapes the contents of "column" within the "data-column" attribute', () => { - expect( - cell({ - filterable: true, - column: '', - timefield: true, - sourcefield: false, - formatted: 'formatted content', - }) - ).toMatchInlineSnapshot(` - "formatted content - " - `); - }); -}); diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.ts deleted file mode 100644 index 8138e0f4a4fd8..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { escape } from 'lodash'; -import cellWithFilters from './cell_with_buttons.html'; -import cellWithoutFilters from './cell_without_buttons.html'; - -const TAGS_WITH_WS = />\s+<'); -} - -const cellWithFiltersTemplate = noWhiteSpace(cellWithFilters); -const cellWithoutFiltersTemplate = noWhiteSpace(cellWithoutFilters); - -interface CellProps { - timefield: boolean; - sourcefield?: boolean; - formatted: string; - filterable: boolean; - column: string; -} - -export const cell = (props: CellProps) => { - let classes = ''; - let extraAttrs = ''; - if (props.timefield) { - classes = 'eui-textNoWrap'; - extraAttrs = 'width="1%"'; - } else if (props.sourcefield) { - classes = 'eui-textBreakAll eui-textBreakWord'; - } else { - classes = 'kbnDocTableCell__dataField eui-textBreakAll eui-textBreakWord'; - } - - if (props.filterable) { - const escapedColumnContents = escape(props.column); - return cellWithFiltersTemplate - .replace('__classes__', classes) - .replace('__extraAttrs__', extraAttrs) - .replace('__column__', escapedColumnContents) - .replace('__column__', escapedColumnContents) - .replace('', props.formatted); - } - return cellWithoutFiltersTemplate - .replace('__classes__', classes) - .replace('__extraAttrs__', extraAttrs) - .replace('', props.formatted); -}; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_with_buttons.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_with_buttons.html deleted file mode 100644 index 99c65e6034013..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_with_buttons.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_without_buttons.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_without_buttons.html deleted file mode 100644 index 8dc33cbfb8353..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell_without_buttons.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html deleted file mode 100644 index faa3d51c19fee..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html +++ /dev/null @@ -1,53 +0,0 @@ - -
-
-
-
- -
-
-

-
-
-
-
-
-
- -
-
- -
-
-
-
-
- -
- - diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html deleted file mode 100644 index 1a5d974a1b081..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.test.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.test.ts deleted file mode 100644 index 70d8465589237..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 { truncateByHeight } from './truncate_by_height'; - -describe('truncateByHeight', () => { - it('renders input without any formatting or escaping', () => { - expect( - truncateByHeight({ - body: - '
 hey you can put HTML & stuff in here 
', - }) - ).toMatchInlineSnapshot( - `"
 hey you can put HTML & stuff in here 
"` - ); - }); -}); diff --git a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_embeddable.tsx b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_embeddable.tsx deleted file mode 100644 index 19913ed6de870..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_embeddable.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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, { useRef, useEffect } from 'react'; -import { I18nProvider } from '@kbn/i18n/react'; -import { IScope } from 'angular'; -import { getServices } from '../../../kibana_services'; -import { DocTableLegacyProps, injectAngularElement } from './create_doc_table_react'; - -type AngularEmbeddableScope = IScope & { renderProps?: DocTableEmbeddableProps }; - -export interface DocTableEmbeddableProps extends Partial { - refs: HTMLElement; -} - -function getRenderFn(domNode: Element, props: DocTableEmbeddableProps) { - const directive = { - template: ``, - }; - - return async () => { - try { - const injector = await getServices().getEmbeddableInjector(); - return await injectAngularElement(domNode, directive.template, props, injector); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - throw e; - } - }; -} - -export function DiscoverDocTableEmbeddable(props: DocTableEmbeddableProps) { - return ( - - - - ); -} - -function DocTableLegacyInner(renderProps: DocTableEmbeddableProps) { - const scope = useRef(); - - useEffect(() => { - if (renderProps.refs && !scope.current) { - const fn = getRenderFn(renderProps.refs, renderProps); - fn().then((newScope) => { - scope.current = newScope; - }); - } else if (scope?.current) { - scope.current.renderProps = { ...renderProps }; - scope.current.$applyAsync(); - } - }, [renderProps]); - - useEffect(() => { - return () => { - scope.current?.$destroy(); - }; - }, []); - return ; -} diff --git a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx deleted file mode 100644 index 73a67310bf4be..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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 angular, { auto, ICompileService, IScope } from 'angular'; -import { render } from 'react-dom'; -import React, { useRef, useEffect, useState, useCallback } from 'react'; -import type { estypes } from '@elastic/elasticsearch'; -import { EuiButtonEmpty } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { getServices, IndexPattern } from '../../../kibana_services'; -import { IndexPatternField } from '../../../../../data/common'; -import { SkipBottomButton } from '../../apps/main/components/skip_bottom_button'; - -export interface DocTableLegacyProps { - columns: string[]; - searchDescription?: string; - searchTitle?: string; - onFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; - rows: estypes.SearchHit[]; - indexPattern: IndexPattern; - minimumVisibleRows?: number; - onAddColumn?: (column: string) => void; - onBackToTop: () => void; - onSort?: (sort: string[][]) => void; - onMoveColumn?: (columns: string, newIdx: number) => void; - onRemoveColumn?: (column: string) => void; - sampleSize: number; - sort?: string[][]; - useNewFieldsApi?: boolean; -} -export interface AngularDirective { - template: string; -} -export type AngularScope = IScope & { renderProps?: DocTableLegacyProps }; - -/** - * Compiles and injects the give angular template into the given dom node - * returns a function to cleanup the injected angular element - */ -export async function injectAngularElement( - domNode: Element, - template: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - renderProps: any, - injector: auto.IInjectorService -) { - const rootScope: IScope = injector.get('$rootScope'); - const $compile: ICompileService = injector.get('$compile'); - const newScope = Object.assign(rootScope.$new(), { renderProps }); - - const $target = angular.element(domNode); - const $element = angular.element(template); - - newScope.$apply(() => { - const linkFn = $compile($element); - $target.empty().append($element); - linkFn(newScope); - }); - - return newScope; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function getRenderFn(domNode: Element, props: any) { - const directive = { - template: ``, - }; - - return async () => { - try { - const injector = await getServices().getEmbeddableInjector(); - return await injectAngularElement(domNode, directive.template, props, injector); - } catch (e) { - render(
error
, domNode); - } - }; -} - -export function DocTableLegacy(renderProps: DocTableLegacyProps) { - const ref = useRef(null); - const scope = useRef(); - const [rows, setRows] = useState(renderProps.rows); - const [minimumVisibleRows, setMinimumVisibleRows] = useState(50); - const onSkipBottomButtonClick = useCallback(async () => { - // delay scrolling to after the rows have been rendered - const bottomMarker = document.getElementById('discoverBottomMarker'); - const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - // show all the rows - setMinimumVisibleRows(renderProps.rows.length); - - while (renderProps.rows.length !== document.getElementsByClassName('kbnDocTable__row').length) { - await wait(50); - } - bottomMarker!.focus(); - await wait(50); - bottomMarker!.blur(); - }, [setMinimumVisibleRows, renderProps.rows]); - - useEffect(() => { - setMinimumVisibleRows(50); - setRows(renderProps.rows); - }, [renderProps.rows, setMinimumVisibleRows]); - - useEffect(() => { - if (ref && ref.current && !scope.current) { - const fn = getRenderFn(ref.current, { ...renderProps, rows, minimumVisibleRows }); - fn().then((newScope) => { - scope.current = newScope; - }); - } else if (scope && scope.current) { - scope.current.renderProps = { ...renderProps, rows, minimumVisibleRows }; - scope.current.$applyAsync(); - } - }, [renderProps, minimumVisibleRows, rows]); - - useEffect(() => { - return () => { - if (scope.current) { - scope.current.$destroy(); - } - }; - }, []); - return ( -
- -
- {renderProps.rows.length === renderProps.sampleSize ? ( -
- - - - -
- ) : ( - - ​ - - )} -
- ); -} diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.html b/src/plugins/discover/public/application/angular/doc_table/doc_table.html deleted file mode 100644 index ecd7aa8f3dcf4..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.html +++ /dev/null @@ -1,121 +0,0 @@ -
-
-
-
-
- {{ limitedResultsWarning }} -
- - - -
-
-
- - - - - -
-
- - -
- - - - - - -
- -
- -
-
- - -
- -

-

-
-
diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js deleted file mode 100644 index 097f32965b141..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 angular from 'angular'; -import _ from 'lodash'; -import 'angular-mocks'; -import 'angular-sanitize'; -import 'angular-route'; -import { createBrowserHistory } from 'history'; -import FixturesStubbedLogstashIndexPatternProvider from '../../../__fixtures__/stubbed_logstash_index_pattern'; -import hits from '../../../__fixtures__/real_hits'; -import { coreMock } from '../../../../../../core/public/mocks'; -import { dataPluginMock } from '../../../../../data/public/mocks'; -import { navigationPluginMock } from '../../../../../navigation/public/mocks'; -import { initAngularBootstrap } from '../../../../../kibana_legacy/public/angular_bootstrap'; -import { setScopedHistory, setServices } from '../../../kibana_services'; -import { getInnerAngularModule } from '../get_inner_angular'; - -let $parentScope; - -let $scope; - -let $timeout; - -let indexPattern; - -const init = function ($elem, props) { - angular.mock.inject(function ($rootScope, $compile, _$timeout_) { - $timeout = _$timeout_; - $parentScope = $rootScope; - _.assign($parentScope, props); - - $compile($elem)($parentScope); - - // I think the prereq requires this? - $timeout(() => { - $elem.scope().$digest(); - }, 0); - - $scope = $elem.isolateScope(); - }); -}; - -const destroy = () => { - $scope.$destroy(); - $parentScope.$destroy(); -}; - -describe('docTable', () => { - const core = coreMock.createStart(); - let $elem; - - beforeAll(async () => { - await initAngularBootstrap(); - }); - beforeAll(() => setScopedHistory(createBrowserHistory())); - beforeEach(() => { - angular.element.prototype.slice = jest.fn(() => { - return null; - }); - angular.element.prototype.filter = jest.fn(() => { - return { - remove: jest.fn(), - }; - }); - setServices({ - uiSettings: core.uiSettings, - }); - getInnerAngularModule( - 'app/discover', - core, - { - data: dataPluginMock.createStartContract(), - navigation: navigationPluginMock.createStartContract(), - }, - coreMock.createPluginInitializerContext() - ); - angular.mock.module('app/discover'); - }); - beforeEach(() => { - $elem = angular.element(` - - `); - angular.mock.inject(function (Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - }); - init($elem, { - indexPattern, - hits: [...hits], - totalHitCount: hits.length, - columns: [], - sorting: ['@timestamp', 'desc'], - }); - $scope.$digest(); - }); - - afterEach(() => { - delete angular.element.prototype.slice; - delete angular.element.prototype.filter; - destroy(); - }); - - test('should compile', () => { - expect($elem.text()).toBeTruthy(); - }); - - test('should have an addRows function that increases the row count', () => { - expect($scope.addRows).toBeInstanceOf(Function); - $scope.$digest(); - expect($scope.limit).toBe(50); - $scope.addRows(); - expect($scope.limit).toBe(100); - }); - - test('should reset the row limit when results are received', () => { - $scope.limit = 100; - expect($scope.limit).toBe(100); - $scope.hits = [...hits]; - $scope.$digest(); - expect($scope.limit).toBe(50); - }); - - test('should have a header and a table element', () => { - $scope.$digest(); - - expect($elem.find('thead').length).toBe(1); - expect($elem.find('table').length).toBe(1); - }); -}); diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts deleted file mode 100644 index 64c045a682296..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 html from './doc_table.html'; -import { dispatchRenderComplete } from '../../../../../kibana_utils/public'; -import { SAMPLE_SIZE_SETTING } from '../../../../common'; -// @ts-expect-error -import { getLimitedSearchResultsMessage } from './doc_table_strings'; -import { getServices } from '../../../kibana_services'; -import './index.scss'; - -export interface LazyScope extends ng.IScope { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function createDocTableDirective(pagerFactory: any, $filter: any) { - return { - restrict: 'E', - template: html, - scope: { - sorting: '=', - columns: '=', - hits: '=', - totalHitCount: '=', - indexPattern: '=', - isLoading: '=?', - infiniteScroll: '=?', - filter: '=?', - minimumVisibleRows: '=?', - onAddColumn: '=?', - onChangeSortOrder: '=?', - onMoveColumn: '=?', - onRemoveColumn: '=?', - inspectorAdapters: '=?', - useNewFieldsApi: '<', - }, - link: ($scope: LazyScope, $el: JQuery) => { - $scope.persist = { - sorting: $scope.sorting, - columns: $scope.columns, - }; - - const limitTo = $filter('limitTo'); - const calculateItemsOnPage = () => { - $scope.pager.setTotalItems($scope.hits.length); - $scope.pageOfItems = limitTo($scope.hits, $scope.pager.pageSize, $scope.pager.startIndex); - }; - - $scope.limitedResultsWarning = getLimitedSearchResultsMessage( - getServices().uiSettings.get(SAMPLE_SIZE_SETTING, 500) - ); - - $scope.addRows = function () { - $scope.limit += 50; - }; - - $scope.$watch('minimumVisibleRows', (minimumVisibleRows: number) => { - $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50); - }); - - $scope.$watch('hits', (hits: unknown[]) => { - if (!hits) return; - - // Reset infinite scroll limit - $scope.limit = $scope.minimumVisibleRows || 50; - - if (hits.length === 0) { - dispatchRenderComplete($el[0]); - } - - if ($scope.infiniteScroll) return; - $scope.pager = pagerFactory.create(hits.length, 50, 1); - calculateItemsOnPage(); - }); - - $scope.pageOfItems = []; - $scope.onPageNext = () => { - $scope.pager.nextPage(); - calculateItemsOnPage(); - }; - - $scope.onPagePrevious = () => { - $scope.pager.previousPage(); - calculateItemsOnPage(); - }; - - $scope.shouldShowLimitedResultsWarning = () => - !$scope.pager.hasNextPage && $scope.pager.totalItems < $scope.totalHitCount; - }, - }; -} diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table_strings.js b/src/plugins/discover/public/application/angular/doc_table/doc_table_strings.js deleted file mode 100644 index aac4b9cfe155d..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table_strings.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -/** - * A message letting the user know the results that have been retrieved is limited - * to a certain size. - * @param resultCount {Number} - */ -export function getLimitedSearchResultsMessage(resultCount) { - return i18n.translate('discover.docTable.limitedSearchResultLabel', { - defaultMessage: 'Limited to {resultCount} results. Refine your search.', - values: { resultCount }, - }); -} diff --git a/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts b/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts deleted file mode 100644 index 2029354376f26..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 $ from 'jquery'; - -interface LazyScope extends ng.IScope { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -} - -export function createInfiniteScrollDirective() { - return { - restrict: 'E', - scope: { - more: '=', - }, - link: ($scope: LazyScope, $element: JQuery) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let checkTimer: any; - /** - * depending on which version of Discover is displayed, different elements are scrolling - * and have therefore to be considered for calculation of infinite scrolling - */ - const scrollDiv = $element.parents('.dscTable'); - const scrollDivMobile = $(window); - - function onScroll() { - if (!$scope.more) return; - const isMobileView = document.getElementsByClassName('dscSidebar__mobile').length > 0; - const usedScrollDiv = isMobileView ? scrollDivMobile : scrollDiv; - const scrollTop = usedScrollDiv.scrollTop(); - const scrollOffset = usedScrollDiv.prop('offsetTop') || 0; - - const winHeight = Number(usedScrollDiv.height()); - const winBottom = Number(winHeight) + Number(scrollTop); - const elTop = $element.get(0).offsetTop || 0; - const remaining = elTop - scrollOffset - winBottom; - - if (remaining <= winHeight) { - $scope[$scope.$$phase ? '$eval' : '$apply'](function () { - $scope.more(); - }); - } - } - - function scheduleCheck() { - if (checkTimer) return; - checkTimer = setTimeout(function () { - checkTimer = null; - onScroll(); - }, 50); - } - - scrollDiv.on('scroll', scheduleCheck); - window.addEventListener('scroll', scheduleCheck); - $scope.$on('$destroy', function () { - clearTimeout(checkTimer); - scrollDiv.off('scroll', scheduleCheck); - window.removeEventListener('scroll', scheduleCheck); - }); - scheduleCheck(); - }, - }; -} diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/pager/index.js b/src/plugins/discover/public/application/angular/doc_table/lib/pager/index.js deleted file mode 100644 index db99dbe76d99f..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/lib/pager/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 './pager_factory'; -export { Pager } from './pager'; diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/pager/pager.js b/src/plugins/discover/public/application/angular/doc_table/lib/pager/pager.js deleted file mode 100644 index 1bd27a8854ca3..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/lib/pager/pager.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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. - */ - -function clamp(val, min, max) { - return Math.min(Math.max(min, val), max); -} - -export class Pager { - constructor(totalItems, pageSize, startingPage) { - this.currentPage = startingPage; - this.totalItems = totalItems; - this.pageSize = pageSize; - this.startIndex = 0; - this.updateMeta(); - } - - get pageCount() { - return Math.ceil(this.totalItems / this.pageSize); - } - - get hasNextPage() { - return this.currentPage < this.totalPages; - } - - get hasPreviousPage() { - return this.currentPage > 1; - } - - nextPage() { - this.currentPage += 1; - this.updateMeta(); - } - - previousPage() { - this.currentPage -= 1; - this.updateMeta(); - } - - setTotalItems(count) { - this.totalItems = count; - this.updateMeta(); - } - - setPageSize(count) { - this.pageSize = count; - this.updateMeta(); - } - - updateMeta() { - this.totalPages = Math.ceil(this.totalItems / this.pageSize); - this.currentPage = clamp(this.currentPage, 1, this.totalPages); - - this.startItem = (this.currentPage - 1) * this.pageSize + 1; - this.startItem = clamp(this.startItem, 0, this.totalItems); - - this.endItem = this.startItem - 1 + this.pageSize; - this.endItem = clamp(this.endItem, 0, this.totalItems); - - this.startIndex = this.startItem - 1; - } -} diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/pager/pager_factory.ts b/src/plugins/discover/public/application/angular/doc_table/lib/pager/pager_factory.ts deleted file mode 100644 index 7cd36d419969e..0000000000000 --- a/src/plugins/discover/public/application/angular/doc_table/lib/pager/pager_factory.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -// @ts-expect-error -import { Pager } from './pager'; - -export function createPagerFactory() { - return { - create(...args: unknown[]) { - return new Pager(...args); - }, - }; -} diff --git a/src/plugins/discover/public/application/angular/get_inner_angular.ts b/src/plugins/discover/public/application/angular/get_inner_angular.ts index 992d82795302b..5f459c369ce4d 100644 --- a/src/plugins/discover/public/application/angular/get_inner_angular.ts +++ b/src/plugins/discover/public/application/angular/get_inner_angular.ts @@ -19,19 +19,8 @@ import { CoreStart, PluginInitializerContext } from 'kibana/public'; import { DataPublicPluginStart } from '../../../../data/public'; import { Storage } from '../../../../kibana_utils/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../navigation/public'; -import { createDocTableDirective } from './doc_table'; -import { createTableHeaderDirective } from './doc_table/components/table_header'; -import { - createToolBarPagerButtonsDirective, - createToolBarPagerTextDirective, -} from './doc_table/components/pager'; import { createContextAppLegacy } from '../components/context_app/context_app_legacy_directive'; -import { createTableRowDirective } from './doc_table/components/table_row'; -import { createPagerFactory } from './doc_table/lib/pager/pager_factory'; -import { createInfiniteScrollDirective } from './doc_table/infinite_scroll'; -import { createDocViewerDirective } from './doc_viewer'; import { createDiscoverGridDirective } from './create_discover_grid_directive'; -import { createRenderCompleteDirective } from './directives/render_complete'; import { configureAppAngularModule, PrivateProvider, @@ -83,7 +72,6 @@ export function initializeInnerAngularModule( createLocalPrivateModule(); createLocalPromiseModule(); createLocalStorageModule(); - createPagerFactoryModule(); createDocTableModule(); initialized = true; } @@ -97,12 +85,10 @@ export function initializeInnerAngularModule( 'discoverI18n', 'discoverPrivate', 'discoverDocTable', - 'discoverPagerFactory', 'discoverPromise', ]) .config(watchMultiDecorator) - .directive('icon', (reactDirective) => reactDirective(EuiIcon)) - .directive('renderComplete', createRenderCompleteDirective); + .directive('icon', (reactDirective) => reactDirective(EuiIcon)); } return angular @@ -116,11 +102,9 @@ export function initializeInnerAngularModule( 'discoverPromise', 'discoverLocalStorageProvider', 'discoverDocTable', - 'discoverPagerFactory', ]) .config(watchMultiDecorator) .run(registerListenEventListener) - .directive('renderComplete', createRenderCompleteDirective) .directive('discover', createDiscoverDirective); } @@ -153,20 +137,9 @@ const createLocalStorageService = function (type: string) { }; }; -function createPagerFactoryModule() { - angular.module('discoverPagerFactory', []).factory('pagerFactory', createPagerFactory); -} - function createDocTableModule() { angular - .module('discoverDocTable', ['discoverPagerFactory', 'react']) - .directive('docTable', createDocTableDirective) - .directive('kbnTableHeader', createTableHeaderDirective) - .directive('toolBarPagerText', createToolBarPagerTextDirective) - .directive('kbnTableRow', createTableRowDirective) - .directive('toolBarPagerButtons', createToolBarPagerButtonsDirective) - .directive('kbnInfiniteScroll', createInfiniteScrollDirective) + .module('discoverDocTable', ['react']) .directive('discoverGrid', createDiscoverGridDirective) - .directive('docViewer', createDocViewerDirective) .directive('contextAppLegacy', createContextAppLegacy); } diff --git a/src/plugins/discover/public/application/angular/helpers/index.ts b/src/plugins/discover/public/application/angular/helpers/index.ts index 6a7f75b7e81a2..a7d9d4581d989 100644 --- a/src/plugins/discover/public/application/angular/helpers/index.ts +++ b/src/plugins/discover/public/application/angular/helpers/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export { formatRow, formatTopLevelObject } from './row_formatter'; export { handleSourceColumnState } from './state_helpers'; export { PromiseServiceCreator } from './promises'; diff --git a/src/plugins/discover/public/application/angular/index.ts b/src/plugins/discover/public/application/angular/index.ts index e75add7910b74..c4f6415c771f9 100644 --- a/src/plugins/discover/public/application/angular/index.ts +++ b/src/plugins/discover/public/application/angular/index.ts @@ -14,5 +14,4 @@ import 'angular-route'; import './discover'; import './doc'; import './context'; -import './doc_viewer'; import './redirect'; diff --git a/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss similarity index 86% rename from src/plugins/discover/public/application/angular/doc_table/_doc_table.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss index ead426fa9c4eb..add2d4e753c60 100644 --- a/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss @@ -2,7 +2,7 @@ * 1. Stack content vertically so the table can scroll when its constrained by a fixed container height. */ // stylelint-disable selector-no-qualifying-type -doc-table { +.kbnDocTableWrapper { @include euiScrollBar; overflow: auto; flex: 1 1 100%; @@ -21,6 +21,18 @@ doc-table { z-index: $euiZLevel1; opacity: .5; } + + // SASSTODO: add a monospace modifier to the doc-table component + .kbnDocTable__row { + font-family: $euiCodeFontFamily; + font-size: $euiFontSizeXS; + } +} + +.kbnDocTable__footer { + background-color: $euiColorLightShade; + padding: $euiSizeXS $euiSizeS; + text-align: center; } .kbnDocTable__container.loading { @@ -28,8 +40,6 @@ doc-table { } .kbnDocTable { - font-size: $euiFontSizeXS; - th { white-space: nowrap; padding-right: $euiSizeS; @@ -84,19 +94,6 @@ doc-table { } } -.kbnDocTable__bar { - margin: $euiSizeXS $euiSizeXS 0; -} - -.kbnDocTable__bar--footer { - position: relative; - margin: -($euiSize * 3) $euiSizeXS 0; -} - -.kbnDocTable__padBottom { - padding-bottom: $euiSizeXL; -} - .kbnDocTable__error { display: flex; flex-direction: column; diff --git a/src/plugins/discover/public/application/angular/doc_table/actions/columns.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts similarity index 86% rename from src/plugins/discover/public/application/angular/doc_table/actions/columns.test.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts index e1aa96f4625de..3b73044b68e07 100644 --- a/src/plugins/discover/public/application/angular/doc_table/actions/columns.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts @@ -7,11 +7,11 @@ */ import { getStateColumnActions } from './columns'; -import { configMock } from '../../../../__mocks__/config'; -import { indexPatternMock } from '../../../../__mocks__/index_pattern'; -import { indexPatternsMock } from '../../../../__mocks__/index_patterns'; -import { Capabilities } from '../../../../../../../core/types'; -import { AppState } from '../../../apps/main/services/discover_state'; +import { configMock } from '../../../../../../__mocks__/config'; +import { indexPatternMock } from '../../../../../../__mocks__/index_pattern'; +import { indexPatternsMock } from '../../../../../../__mocks__/index_patterns'; +import { Capabilities } from '../../../../../../../../../core/types'; +import { AppState } from '../../../services/discover_state'; function getStateColumnAction(state: {}, setAppState: (state: Partial) => void) { return getStateColumnActions({ diff --git a/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts similarity index 92% rename from src/plugins/discover/public/application/angular/doc_table/actions/columns.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts index 9ef5d45947afb..130b43539d9b5 100644 --- a/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts @@ -6,17 +6,17 @@ * Side Public License, v 1. */ import { Capabilities, IUiSettingsClient } from 'kibana/public'; -import { popularizeField } from '../../../helpers/popularize_field'; -import { IndexPattern, IndexPatternsContract } from '../../../../kibana_services'; +import { SORT_DEFAULT_ORDER_SETTING } from '../../../../../../../common'; +import { popularizeField } from '../../../../../../application/helpers/popularize_field'; import { AppState as DiscoverState, GetStateReturn as DiscoverGetStateReturn, -} from '../../../apps/main/services/discover_state'; +} from '../../../../../../application/apps/main/services/discover_state'; import { AppState as ContextState, GetStateReturn as ContextGetStateReturn, -} from '../../context_state'; -import { SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; +} from '../../../../../../application/angular/context_state'; +import { IndexPattern, IndexPatternsContract } from '../../../../../../../../data/public'; /** * Helper function to provide a fallback to a single _source column if the given array of columns diff --git a/src/plugins/discover/public/application/angular/doc_table/components/_index.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/components/_index.scss similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/_index.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/_index.scss diff --git a/src/plugins/discover/public/application/angular/doc_table/components/_table_header.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/components/_table_header.scss similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/_table_header.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/_table_header.scss diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/pager/tool_bar_pagination.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/pager/tool_bar_pagination.tsx new file mode 100644 index 0000000000000..878a9b8162628 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/pager/tool_bar_pagination.tsx @@ -0,0 +1,108 @@ +/* + * 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, { useState } from 'react'; +import { + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiPagination, + EuiPopover, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n/'; + +interface ToolBarPaginationProps { + pageSize: number; + pageCount: number; + activePage: number; + onPageClick: (page: number) => void; + onPageSizeChange: (size: number) => void; +} + +export const ToolBarPagination = ({ + pageSize, + pageCount, + activePage, + onPageSizeChange, + onPageClick, +}: ToolBarPaginationProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const rowsWord = i18n.translate('discover.docTable.rows', { + defaultMessage: 'rows', + }); + + const onChooseRowsClick = () => setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen); + + const closePopover = () => setIsPopoverOpen(false); + + const getIconType = (size: number) => { + return size === pageSize ? 'check' : 'empty'; + }; + + const rowsPerPageOptions = [25, 50, 100].map((cur) => ( + { + closePopover(); + onPageSizeChange(cur); + }} + > + {cur} {rowsWord} + + )); + + return ( + + + + + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + > + + + + + + + + + ); +}; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/helpers.tsx similarity index 97% rename from src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/helpers.tsx index a75aea7169737..5afa26d35b4f5 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/helpers.tsx @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import { IndexPattern } from '../../../../../kibana_services'; +import { IndexPattern } from '../../../../../../../kibana_services'; export type SortOrder = [string, string]; export interface ColumnProps { diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header.test.tsx similarity index 99% rename from src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header.test.tsx index 48ea7ffc46384..7b72e94169cfe 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header.test.tsx @@ -11,7 +11,7 @@ import { mountWithIntl } from '@kbn/test/jest'; import { TableHeader } from './table_header'; import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; -import { IndexPattern, IndexPatternField } from '../../../../../kibana_services'; +import { IndexPattern, IndexPatternField } from '../../../../../../../kibana_services'; function getMockIndexPattern() { return ({ diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header.tsx similarity index 96% rename from src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header.tsx index 57f7382bd98ca..cb8198f1d6d6a 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { IndexPattern } from '../../../../../kibana_services'; +import { IndexPattern } from '../../../../../../../kibana_services'; import { TableHeaderColumn } from './table_header_column'; import { SortOrder, getDisplayedColumns } from './helpers'; import { getDefaultSort } from '../../lib/get_default_sort'; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header_column.tsx similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_header/table_header_column.tsx diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.test.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.test.tsx new file mode 100644 index 0000000000000..59ced9d5668a0 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.test.tsx @@ -0,0 +1,107 @@ +/* + * 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, findTestSubject } from '@kbn/test/jest'; +import { TableRow, TableRowProps } from './table_row'; +import { setDocViewsRegistry, setServices } from '../../../../../../kibana_services'; +import { createFilterManagerMock } from '../../../../../../../../data/public/query/filter_manager/filter_manager.mock'; +import { DiscoverServices } from '../../../../../../build_services'; +import { indexPatternWithTimefieldMock } from '../../../../../../__mocks__/index_pattern_with_timefield'; +import { uiSettingsMock } from '../../../../../../__mocks__/ui_settings'; +import { DocViewsRegistry } from '../../../../../doc_views/doc_views_registry'; + +jest.mock('../lib/row_formatter', () => { + const originalModule = jest.requireActual('../lib/row_formatter'); + return { + ...originalModule, + formatRow: () => mocked_document_cell, + }; +}); + +const mountComponent = (props: TableRowProps) => { + return mountWithIntl( + + + + +
+ ); +}; + +const mockHit = { + _index: 'mock_index', + _id: '1', + _score: 1, + _type: '_doc', + fields: [ + { + timestamp: '2020-20-01T12:12:12.123', + }, + ], + _source: { message: 'mock_message', bytes: 20 }, +}; + +const mockFilterManager = createFilterManagerMock(); + +describe('Doc table row component', () => { + let mockInlineFilter; + let defaultProps: TableRowProps; + + beforeEach(() => { + mockInlineFilter = jest.fn(); + + defaultProps = ({ + columns: ['_source'], + filter: mockInlineFilter, + indexPattern: indexPatternWithTimefieldMock, + row: mockHit, + useNewFieldsApi: true, + filterManager: mockFilterManager, + addBasePath: (path: string) => path, + hideTimeColumn: true, + } as unknown) as TableRowProps; + + setServices(({ + uiSettings: uiSettingsMock, + } as unknown) as DiscoverServices); + + setDocViewsRegistry(new DocViewsRegistry()); + }); + + it('should render __document__ column', () => { + const component = mountComponent({ ...defaultProps, columns: [] }); + const docTableField = findTestSubject(component, 'docTableField'); + expect(docTableField.first().text()).toBe('mocked_document_cell'); + }); + + it('should render message, _index and bytes fields', () => { + const component = mountComponent({ ...defaultProps, columns: ['message', '_index', 'bytes'] }); + + const fields = findTestSubject(component, 'docTableField'); + expect(fields.first().text()).toBe('mock_message'); + expect(fields.last().text()).toBe('20'); + expect(fields.length).toBe(3); + }); + + describe('details row', () => { + it('should be empty by default', () => { + const component = mountComponent(defaultProps); + expect(findTestSubject(component, 'docTableRowDetailsTitle').exists()).toBeFalsy(); + }); + + it('should expand the detail row when the toggle arrow is clicked', () => { + const component = mountComponent(defaultProps); + const toggleButton = findTestSubject(component, 'docTableExpandToggleColumn'); + + expect(findTestSubject(component, 'docTableRowDetailsTitle').exists()).toBeFalsy(); + toggleButton.simulate('click'); + expect(findTestSubject(component, 'docTableRowDetailsTitle').exists()).toBeTruthy(); + }); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx new file mode 100644 index 0000000000000..886aeffc06667 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row.tsx @@ -0,0 +1,204 @@ +/* + * 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, { Fragment, useCallback, useMemo, useState } from 'react'; +import classNames from 'classnames'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty, EuiIcon } from '@elastic/eui'; +import { DocViewer } from '../../../../../components/doc_viewer/doc_viewer'; +import { FilterManager, IndexPattern } from '../../../../../../../../data/public'; +import { TableCell } from './table_row/table_cell'; +import { ElasticSearchHit, DocViewFilterFn } from '../../../../../doc_views/doc_views_types'; +import { trimAngularSpan } from '../../../../../components/table/table_helper'; +import { getContextUrl } from '../../../../../helpers/get_context_url'; +import { getSingleDocUrl } from '../../../../../helpers/get_single_doc_url'; +import { TableRowDetails } from './table_row_details'; +import { formatRow, formatTopLevelObject } from '../lib/row_formatter'; + +export type DocTableRow = ElasticSearchHit & { + isAnchor?: boolean; +}; + +export interface TableRowProps { + columns: string[]; + filter: DocViewFilterFn; + indexPattern: IndexPattern; + row: DocTableRow; + onAddColumn?: (column: string) => void; + onRemoveColumn?: (column: string) => void; + useNewFieldsApi: boolean; + hideTimeColumn: boolean; + filterManager: FilterManager; + addBasePath: (path: string) => string; +} + +export const TableRow = ({ + columns, + filter, + row, + indexPattern, + useNewFieldsApi, + hideTimeColumn, + onAddColumn, + onRemoveColumn, + filterManager, + addBasePath, +}: TableRowProps) => { + const [open, setOpen] = useState(false); + const docTableRowClassName = classNames('kbnDocTable__row', { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'kbnDocTable__row--highlight': row.isAnchor, + }); + const anchorDocTableRowSubj = row.isAnchor ? ' docTableAnchorRow' : ''; + + const flattenedRow = useMemo(() => indexPattern.flattenHit(row), [indexPattern, row]); + const mapping = useMemo(() => indexPattern.fields.getByName, [indexPattern]); + + // toggle display of the rows details, a full list of the fields from each row + const toggleRow = () => setOpen((prevOpen) => !prevOpen); + + /** + * Fill an element with the value of a field + */ + const displayField = (fieldName: string) => { + const text = indexPattern.formatField(row, fieldName); + const formattedField = trimAngularSpan(String(text)); + + // field formatters take care of escaping + // eslint-disable-next-line react/no-danger + const fieldElement = ; + + return
{fieldElement}
; + }; + const inlineFilter = useCallback( + (column: string, type: '+' | '-') => { + const field = indexPattern.fields.getByName(column); + filter(field!, flattenedRow[column], type); + }, + [filter, flattenedRow, indexPattern.fields] + ); + + const getContextAppHref = () => { + return getContextUrl(row._id, indexPattern.id!, columns, filterManager, addBasePath); + }; + + const getSingleDocHref = () => { + return addBasePath(getSingleDocUrl(indexPattern.id!, row._index, row._id)); + }; + + const rowCells = [ + + + {open ? ( + + ) : ( + + )} + + , + ]; + + if (indexPattern.timeFieldName && !hideTimeColumn) { + rowCells.push( + + ); + } + + if (columns.length === 0 && useNewFieldsApi) { + const formatted = formatRow(row, indexPattern); + + rowCells.push( + + ); + } else { + columns.forEach(function (column: string) { + // when useNewFieldsApi is true, addressing to the fields property is safe + if (useNewFieldsApi && !mapping(column) && !row.fields![column]) { + const innerColumns = Object.fromEntries( + Object.entries(row.fields!).filter(([key]) => { + return key.indexOf(`${column}.`) === 0; + }) + ); + + rowCells.push( + + ); + } else { + const isFilterable = Boolean(mapping(column)?.filterable && filter); + rowCells.push( + + ); + } + }); + } + + return ( + + + {rowCells} + + + + + + + + ); +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap new file mode 100644 index 0000000000000..5f3564174adf8 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/__snapshots__/table_cell.test.tsx.snap @@ -0,0 +1,183 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Doc table cell component renders a cell with filter buttons if it is filterable 1`] = ` + + formatted content +
+ } + inlineFilter={[Function]} + sourcefield={false} + timefield={true} +> + + + formatted content + + + + + + + + + + + + + + + + + +`; + +exports[`Doc table cell component renders a cell without filter buttons if it is not filterable 1`] = ` + + formatted content + + } + inlineFilter={[Function]} + sourcefield={false} + timefield={true} +> + + + formatted content + + + + +`; + +exports[`Doc table cell component renders a field that is neither a timefield or sourcefield 1`] = ` + + formatted content + + } + inlineFilter={[Function]} + sourcefield={false} + timefield={false} +> + + + formatted content + + + + +`; + +exports[`Doc table cell component renders a sourcefield 1`] = ` + + formatted content + + } + inlineFilter={[Function]} + sourcefield={true} + timefield={false} +> + + + formatted content + + + + +`; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_cell.scss similarity index 64% rename from src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_cell.scss index e175a2f3383e2..2e643c195208c 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_cell.scss @@ -3,7 +3,15 @@ } .kbnDocTableCell__toggleDetails { - padding: $euiSizeXS 0 0 0!important; + padding: $euiSizeXS 0 0 0 !important; +} + +/** + * Fixes time column width in Firefox after toggle display of the rows details. + * Described issue - https://github.com/elastic/kibana/pull/104361#issuecomment-894271241 + */ +.kbnDocTableCell--extraWidth { + width: 1%; } .kbnDocTableCell__filter { @@ -12,6 +20,11 @@ right: 0; } +.kbnDocTableCell__filterButton { + font-size: $euiFontSizeXS; + padding: $euiSizeXS; +} + /** * 1. Align icon with text in cell. * 2. Use opacity to make this element accessible to screen readers and keyboard. diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/_details.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_details.scss similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/table_row/_details.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_details.scss diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/_index.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_index.scss similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/table_row/_index.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_index.scss diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/_open.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_open.scss similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/components/table_row/_open.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/_open.scss diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell.test.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell.test.tsx new file mode 100644 index 0000000000000..316c2b27357a9 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell.test.tsx @@ -0,0 +1,64 @@ +/* + * 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 { mount } from 'enzyme'; +import { CellProps, TableCell } from './table_cell'; + +const mountComponent = (props: Omit) => { + return mount( {}} />); +}; + +describe('Doc table cell component', () => { + test('renders a cell without filter buttons if it is not filterable', () => { + const component = mountComponent({ + filterable: false, + column: 'foo', + timefield: true, + sourcefield: false, + formatted: formatted content, + }); + expect(component).toMatchSnapshot(); + }); + + it('renders a cell with filter buttons if it is filterable', () => { + expect( + mountComponent({ + filterable: true, + column: 'foo', + timefield: true, + sourcefield: false, + formatted: formatted content, + }) + ).toMatchSnapshot(); + }); + + it('renders a sourcefield', () => { + expect( + mountComponent({ + filterable: false, + column: 'foo', + timefield: false, + sourcefield: true, + formatted: formatted content, + }) + ).toMatchSnapshot(); + }); + + it('renders a field that is neither a timefield or sourcefield', () => { + expect( + mountComponent({ + filterable: false, + column: 'foo', + timefield: false, + sourcefield: false, + formatted: formatted content, + }) + ).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell.tsx new file mode 100644 index 0000000000000..ad2368439d6d8 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell.tsx @@ -0,0 +1,42 @@ +/* + * 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 classNames from 'classnames'; +import { TableCellActions } from './table_cell_actions'; +export interface CellProps { + timefield: boolean; + sourcefield?: boolean; + formatted: JSX.Element; + filterable: boolean; + column: string; + inlineFilter: (column: string, type: '+' | '-') => void; +} + +export const TableCell = (props: CellProps) => { + const classes = classNames({ + ['eui-textNoWrap kbnDocTableCell--extraWidth']: props.timefield, + ['eui-textBreakAll eui-textBreakWord']: props.sourcefield, + ['kbnDocTableCell__dataField eui-textBreakAll eui-textBreakWord']: + !props.timefield && !props.sourcefield, + }); + + const handleFilterFor = () => props.inlineFilter(props.column, '+'); + const handleFilterOut = () => props.inlineFilter(props.column, '-'); + + return ( + + {props.formatted} + {props.filterable ? ( + + ) : ( + + )} + + ); +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell_actions.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell_actions.tsx new file mode 100644 index 0000000000000..f252c8d801399 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row/table_cell_actions.tsx @@ -0,0 +1,60 @@ +/* + * 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 { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface TableCellActionsProps { + handleFilterFor: () => void; + handleFilterOut: () => void; +} + +export const TableCellActions = ({ handleFilterFor, handleFilterOut }: TableCellActionsProps) => { + return ( + + + + + + + + + + ); +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row_details.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row_details.tsx new file mode 100644 index 0000000000000..c3ff53fe2d3a8 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/components/table_row_details.tsx @@ -0,0 +1,80 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface TableRowDetailsProps { + open: boolean; + colLength: number; + isTimeBased: boolean; + getContextAppHref: () => string; + getSingleDocHref: () => string; + children: JSX.Element; +} + +export const TableRowDetails = ({ + open, + colLength, + isTimeBased, + getContextAppHref, + getSingleDocHref, + children, +}: TableRowDetailsProps) => { + if (!open) { + return null; + } + + return ( + + + + + + + + + +

+ +

+
+
+
+
+ + + + {isTimeBased && ( + + + + )} + + + + + + + + +
+
{children}
+ + ); +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/create_doc_table_embeddable.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/create_doc_table_embeddable.tsx new file mode 100644 index 0000000000000..c745fbf64d294 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/create_doc_table_embeddable.tsx @@ -0,0 +1,35 @@ +/* + * 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 { I18nProvider } from '@kbn/i18n/react'; +import { DocTableEmbeddable, DocTableEmbeddableProps } from './doc_table_embeddable'; + +export function DiscoverDocTableEmbeddable(renderProps: DocTableEmbeddableProps) { + return ( + + + + ); +} diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_context.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_context.tsx new file mode 100644 index 0000000000000..8d29efec73716 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_context.tsx @@ -0,0 +1,33 @@ +/* + * 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, { Fragment } from 'react'; +import './index.scss'; +import { SkipBottomButton } from '../skip_bottom_button'; +import { DocTableProps, DocTableRenderProps, DocTableWrapper } from './doc_table_wrapper'; + +const DocTableWrapperMemoized = React.memo(DocTableWrapper); + +const renderDocTable = (tableProps: DocTableRenderProps) => { + return ( + + + + {tableProps.renderHeader()} + {tableProps.renderRows(tableProps.rows)} +
+ + ​ + +
+ ); +}; + +export const DocTableContext = (props: DocTableProps) => { + return ; +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx new file mode 100644 index 0000000000000..04902af692b74 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_embeddable.tsx @@ -0,0 +1,114 @@ +/* + * 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, { memo, useCallback, useMemo } from 'react'; +import './index.scss'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { SAMPLE_SIZE_SETTING } from '../../../../../../common'; +import { usePager } from './lib/use_pager'; +import { ToolBarPagination } from './components/pager/tool_bar_pagination'; +import { DocTableProps, DocTableRenderProps, DocTableWrapper } from './doc_table_wrapper'; +import { TotalDocuments } from '../total_documents/total_documents'; +import { getServices } from '../../../../../kibana_services'; + +export interface DocTableEmbeddableProps extends DocTableProps { + totalHitCount: number; +} + +const DocTableWrapperMemoized = memo(DocTableWrapper); + +export const DocTableEmbeddable = (props: DocTableEmbeddableProps) => { + const pager = usePager({ totalItems: props.rows.length }); + + const pageOfItems = useMemo( + () => props.rows.slice(pager.startIndex, pager.pageSize + pager.startIndex), + [pager.pageSize, pager.startIndex, props.rows] + ); + + const shouldShowLimitedResultsWarning = () => + !pager.hasNextPage && props.rows.length < props.totalHitCount; + + const scrollTop = () => { + const scrollDiv = document.querySelector('.kbnDocTableWrapper') as HTMLElement; + scrollDiv.scrollTo(0, 0); + }; + + const onPageChange = (page: number) => { + scrollTop(); + pager.onPageChange(page); + }; + + const onPageSizeChange = (size: number) => { + scrollTop(); + pager.onPageSizeChange(size); + }; + + const sampleSize = useMemo(() => { + return getServices().uiSettings.get(SAMPLE_SIZE_SETTING, 500); + }, []); + + const renderDocTable = useCallback( + (renderProps: DocTableRenderProps) => { + return ( +
+ + {renderProps.renderHeader()} + {renderProps.renderRows(pageOfItems)} +
+
+ ); + }, + [pageOfItems] + ); + + return ( + + + + {shouldShowLimitedResultsWarning() && ( + + + + + + )} + {props.totalHitCount !== 0 && ( + + + + )} + + + + + + + + + + + + ); +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx new file mode 100644 index 0000000000000..8e9066151b368 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx @@ -0,0 +1,113 @@ +/* + * 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, { Fragment, memo, useCallback, useEffect, useState } from 'react'; +import './index.scss'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { debounce } from 'lodash'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { DocTableProps, DocTableRenderProps, DocTableWrapper } from './doc_table_wrapper'; +import { SkipBottomButton } from '../skip_bottom_button'; + +const DocTableInfiniteContent = (props: DocTableRenderProps) => { + const [limit, setLimit] = useState(props.minimumVisibleRows); + + // Reset infinite scroll limit + useEffect(() => { + setLimit(props.minimumVisibleRows); + }, [props.rows, props.minimumVisibleRows]); + + /** + * depending on which version of Discover is displayed, different elements are scrolling + * and have therefore to be considered for calculation of infinite scrolling + */ + useEffect(() => { + const scrollDiv = document.querySelector('.kbnDocTableWrapper') as HTMLElement; + const scrollMobileElem = document.documentElement; + + const scheduleCheck = debounce(() => { + const isMobileView = document.getElementsByClassName('dscSidebar__mobile').length > 0; + const usedScrollDiv = isMobileView ? scrollMobileElem : scrollDiv; + + const scrollusedHeight = usedScrollDiv.scrollHeight; + const scrollTop = Math.abs(usedScrollDiv.scrollTop); + const clientHeight = usedScrollDiv.clientHeight; + + if (scrollTop + clientHeight === scrollusedHeight) { + setLimit((prevLimit) => prevLimit + 50); + } + }, 50); + + scrollDiv.addEventListener('scroll', scheduleCheck); + window.addEventListener('scroll', scheduleCheck); + + scheduleCheck(); + + return () => { + scrollDiv.removeEventListener('scroll', scheduleCheck); + window.removeEventListener('scroll', scheduleCheck); + }; + }, []); + + const onBackToTop = useCallback(() => { + const isMobileView = document.getElementsByClassName('dscSidebar__mobile').length > 0; + const focusElem = document.querySelector('.dscTable') as HTMLElement; + focusElem.focus(); + + // Only the desktop one needs to target a specific container + if (!isMobileView) { + const scrollDiv = document.querySelector('.kbnDocTableWrapper') as HTMLElement; + scrollDiv.scrollTo(0, 0); + } else if (window) { + window.scrollTo(0, 0); + } + }, []); + + return ( + + + + {props.renderHeader()} + {props.renderRows(props.rows.slice(0, limit))} +
+ {props.rows.length === props.sampleSize ? ( +
+ + + + +
+ ) : ( + + ​ + + )} +
+ ); +}; + +const DocTableWrapperMemoized = memo(DocTableWrapper); +const DocTableInfiniteContentMemoized = memo(DocTableInfiniteContent); + +const renderDocTable = (tableProps: DocTableRenderProps) => ( + +); + +export const DocTableInfinite = (props: DocTableProps) => { + return ; +}; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.test.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.test.tsx new file mode 100644 index 0000000000000..df5869bd61e52 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.test.tsx @@ -0,0 +1,75 @@ +/* + * 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 { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { setServices } from '../../../../../kibana_services'; +import { indexPatternMock } from '../../../../../__mocks__/index_pattern'; +import { DocTableWrapper, DocTableWrapperProps } from './doc_table_wrapper'; +import { DocTableRow } from './components/table_row'; +import { discoverServiceMock } from '../../../../../__mocks__/services'; + +const mountComponent = (props: DocTableWrapperProps) => { + return mountWithIntl(); +}; + +describe('Doc table component', () => { + let defaultProps: DocTableWrapperProps; + + const initDefaults = (rows?: DocTableRow[]) => { + defaultProps = { + columns: ['_source'], + indexPattern: indexPatternMock, + rows: rows || [ + { + _index: 'mock_index', + _id: '1', + _score: 1, + _type: '_doc', + fields: [ + { + timestamp: '2020-20-01T12:12:12.123', + }, + ], + _source: { message: 'mock_message', bytes: 20 }, + }, + ], + sort: [['order_date', 'desc']], + isLoading: false, + searchDescription: '', + onAddColumn: () => {}, + onFilter: () => {}, + onMoveColumn: () => {}, + onRemoveColumn: () => {}, + onSort: () => {}, + useNewFieldsApi: true, + dataTestSubj: 'discoverDocTable', + render: () => { + return
mock
; + }, + }; + + setServices(discoverServiceMock); + }; + + it('should render infinite table correctly', () => { + initDefaults(); + const component = mountComponent(defaultProps); + expect(findTestSubject(component, defaultProps.dataTestSubj).exists()).toBeTruthy(); + expect(findTestSubject(component, 'docTable').exists()).toBeTruthy(); + expect(component.find('.kbnDocTable__error').exists()).toBeFalsy(); + }); + + it('should render error fallback if rows array is empty', () => { + initDefaults([]); + const component = mountComponent(defaultProps); + expect(findTestSubject(component, defaultProps.dataTestSubj).exists()).toBeTruthy(); + expect(findTestSubject(component, 'docTable').exists()).toBeFalsy(); + expect(component.find('.kbnDocTable__error').exists()).toBeTruthy(); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx new file mode 100644 index 0000000000000..c875bf155bd79 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx @@ -0,0 +1,243 @@ +/* + * 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, useMemo, useState } from 'react'; +import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { TableHeader } from './components/table_header/table_header'; +import { FORMATS_UI_SETTINGS } from '../../../../../../../field_formats/common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + SAMPLE_SIZE_SETTING, + SORT_DEFAULT_ORDER_SETTING, +} from '../../../../../../common'; +import { getServices, IndexPattern } from '../../../../../kibana_services'; +import { SortOrder } from './components/table_header/helpers'; +import { DocTableRow, TableRow } from './components/table_row'; +import { DocViewFilterFn } from '../../../../doc_views/doc_views_types'; + +export interface DocTableProps { + /** + * Rows of classic table + */ + rows: DocTableRow[]; + /** + * Columns of classic table + */ + columns: string[]; + /** + * Current IndexPattern + */ + indexPattern: IndexPattern; + /** + * Current sorting + */ + sort: string[][]; + /** + * New fields api switch + */ + useNewFieldsApi: boolean; + /** + * Current search description + */ + searchDescription?: string; + /** + * Current shared item title + */ + sharedItemTitle?: string; + /** + * Current data test subject + */ + dataTestSubj: string; + /** + * Loading state + */ + isLoading: boolean; + /** + * Filter callback + */ + onFilter: DocViewFilterFn; + /** + * Sorting callback + */ + onSort?: (sort: string[][]) => void; + /** + * Add columns callback + */ + onAddColumn?: (column: string) => void; + /** + * Reordering column callback + */ + onMoveColumn?: (columns: string, newIdx: number) => void; + /** + * Remove column callback + */ + onRemoveColumn?: (column: string) => void; +} + +export interface DocTableRenderProps { + rows: DocTableRow[]; + minimumVisibleRows: number; + sampleSize: number; + renderRows: (row: DocTableRow[]) => JSX.Element[]; + renderHeader: () => JSX.Element; + onSkipBottomButtonClick: () => void; +} + +export interface DocTableWrapperProps extends DocTableProps { + /** + * Renders Doc table content + */ + render: (params: DocTableRenderProps) => JSX.Element; +} + +export const DocTableWrapper = ({ + render, + columns, + rows, + indexPattern, + onSort, + onAddColumn, + onMoveColumn, + onRemoveColumn, + sort, + onFilter, + useNewFieldsApi, + searchDescription, + sharedItemTitle, + dataTestSubj, + isLoading, +}: DocTableWrapperProps) => { + const [minimumVisibleRows, setMinimumVisibleRows] = useState(50); + const [ + defaultSortOrder, + hideTimeColumn, + isShortDots, + sampleSize, + filterManager, + addBasePath, + ] = useMemo(() => { + const services = getServices(); + return [ + services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), + services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + services.uiSettings.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE), + services.uiSettings.get(SAMPLE_SIZE_SETTING, 500), + services.filterManager, + services.addBasePath, + ]; + }, []); + + const onSkipBottomButtonClick = useCallback(async () => { + // delay scrolling to after the rows have been rendered + const bottomMarker = document.getElementById('discoverBottomMarker'); + const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + // show all the rows + setMinimumVisibleRows(rows.length); + + while (rows.length !== document.getElementsByClassName('kbnDocTable__row').length) { + await wait(50); + } + bottomMarker!.focus(); + await wait(50); + bottomMarker!.blur(); + }, [setMinimumVisibleRows, rows]); + + const renderHeader = useCallback( + () => ( + + ), + [ + columns, + defaultSortOrder, + hideTimeColumn, + indexPattern, + isShortDots, + onMoveColumn, + onRemoveColumn, + onSort, + sort, + ] + ); + + const renderRows = useCallback( + (rowsToRender: DocTableRow[]) => { + return rowsToRender.map((current) => ( + + )); + }, + [ + columns, + onFilter, + indexPattern, + useNewFieldsApi, + hideTimeColumn, + onAddColumn, + onRemoveColumn, + filterManager, + addBasePath, + ] + ); + + return ( +
+ {rows.length !== 0 && + render({ + rows, + minimumVisibleRows, + sampleSize, + onSkipBottomButtonClick, + renderHeader, + renderRows, + })} + {!rows.length && ( +
+ + + + + +
+ )} +
+ ); +}; diff --git a/src/plugins/discover/public/application/angular/doc_table/index.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/index.scss similarity index 100% rename from src/plugins/discover/public/application/angular/doc_table/index.scss rename to src/plugins/discover/public/application/apps/main/components/doc_table/index.scss diff --git a/src/plugins/discover/public/application/angular/doc_table/index.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/index.ts similarity index 90% rename from src/plugins/discover/public/application/angular/doc_table/index.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/index.ts index 3a8f170f8680d..513183cc99468 100644 --- a/src/plugins/discover/public/application/angular/doc_table/index.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export { createDocTableDirective } from './doc_table'; export { getSort, getSortArray } from './lib/get_sort'; export { getSortForSearchSource } from './lib/get_sort_for_search_source'; export { getDefaultSort } from './lib/get_default_sort'; diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_default_sort.test.ts similarity index 91% rename from src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_default_sort.test.ts index f181d583f0211..b2c7499b4a040 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_default_sort.test.ts @@ -8,8 +8,8 @@ import { getDefaultSort } from './get_default_sort'; // @ts-expect-error -import FixturesStubbedLogstashIndexPatternProvider from '../../../../__fixtures__/stubbed_logstash_index_pattern'; -import { IndexPattern } from '../../../../kibana_services'; +import FixturesStubbedLogstashIndexPatternProvider from '../../../../../../__fixtures__/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../../../kibana_services'; describe('getDefaultSort function', function () { let indexPattern: IndexPattern; diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_default_sort.ts similarity index 93% rename from src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_default_sort.ts index aa1cf4a61066d..e01ff0b00e2b0 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_default_sort.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { IndexPattern } from '../../../../kibana_services'; +import { IndexPattern } from '../../../../../../kibana_services'; import { isSortable } from './get_sort'; import { SortOrder } from '../components/table_header/helpers'; diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort.test.ts similarity index 96% rename from src/plugins/discover/public/application/angular/doc_table/lib/get_sort.test.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort.test.ts index 19d629e14da66..865ef1d3fb729 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort.test.ts @@ -8,8 +8,8 @@ import { getSort, getSortArray } from './get_sort'; // @ts-expect-error -import FixturesStubbedLogstashIndexPatternProvider from '../../../../__fixtures__/stubbed_logstash_index_pattern'; -import { IndexPattern } from '../../../../kibana_services'; +import FixturesStubbedLogstashIndexPatternProvider from '../../../../../../__fixtures__/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../../../kibana_services'; describe('docTable', function () { let indexPattern: IndexPattern; diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort.ts similarity index 97% rename from src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort.ts index 4b16c1aa3dcc6..2c687a59ea291 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort.ts @@ -7,7 +7,7 @@ */ import _ from 'lodash'; -import { IndexPattern } from '../../../../../../data/public'; +import { IndexPattern } from '../../../../../../../../data/public'; export type SortPairObj = Record; export type SortPairArr = [string, string]; diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort_for_search_source.test.ts similarity index 94% rename from src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort_for_search_source.test.ts index dc7817d95dd38..3753597ced163 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort_for_search_source.test.ts @@ -8,8 +8,8 @@ import { getSortForSearchSource } from './get_sort_for_search_source'; // @ts-expect-error -import FixturesStubbedLogstashIndexPatternProvider from '../../../../__fixtures__/stubbed_logstash_index_pattern'; -import { IndexPattern } from '../../../../kibana_services'; +import FixturesStubbedLogstashIndexPatternProvider from '../../../../../../__fixtures__/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../../../kibana_services'; import { SortOrder } from '../components/table_header/helpers'; describe('getSortForSearchSource function', function () { diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort_for_search_source.ts similarity index 95% rename from src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort_for_search_source.ts index 58a690b70529e..2bc8a71301df9 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/get_sort_for_search_source.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EsQuerySortValue, IndexPattern } from '../../../../kibana_services'; +import { EsQuerySortValue, IndexPattern } from '../../../../../../kibana_services'; import { SortOrder } from '../components/table_header/helpers'; import { getSort } from './get_sort'; diff --git a/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts similarity index 53% rename from src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts index 6b356446850e6..8c108e7d4dcf6 100644 --- a/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.test.ts @@ -6,12 +6,13 @@ * Side Public License, v 1. */ +import ReactDOM from 'react-dom/server'; import { formatRow, formatTopLevelObject } from './row_formatter'; -import { stubbedSavedObjectIndexPattern } from '../../../__mocks__/stubbed_saved_object_index_pattern'; -import { IndexPattern } from '../../../../../data/common/index_patterns/index_patterns'; -import { fieldFormatsMock } from '../../../../../field_formats/common/mocks'; -import { setServices } from '../../../kibana_services'; -import { DiscoverServices } from '../../../build_services'; +import { stubbedSavedObjectIndexPattern } from '../../../../../../__mocks__/stubbed_saved_object_index_pattern'; +import { IndexPattern } from '../../../../../../../../data/common/index_patterns/index_patterns'; +import { fieldFormatsMock } from '../../../../../../../../field_formats/common/mocks'; +import { setServices } from '../../../../../../kibana_services'; +import { DiscoverServices } from '../../../../../../build_services'; describe('Row formatter', () => { const hit = { @@ -68,9 +69,42 @@ describe('Row formatter', () => { }); it('formats document properly', () => { - expect(formatRow(hit, indexPattern).trim()).toMatchInlineSnapshot( - `"
also:
with \\\\"quotes\\\\" or 'single qoutes'
foo:
bar
number:
42
hello:
<h1>World</h1>
_id:
a
_type:
doc
_score:
1
"` - ); + expect(formatRow(hit, indexPattern)).toMatchInlineSnapshot(` + + `); }); it('limits number of rendered items', () => { @@ -79,17 +113,57 @@ describe('Row formatter', () => { get: () => 1, }, } as unknown) as DiscoverServices); - expect(formatRow(hit, indexPattern).trim()).toMatchInlineSnapshot( - `"
also:
with \\\\"quotes\\\\" or 'single qoutes'
"` - ); + expect(formatRow(hit, indexPattern)).toMatchInlineSnapshot(` + + `); }); it('formats document with highlighted fields first', () => { - expect( - formatRow({ ...hit, highlight: { number: '42' } }, indexPattern).trim() - ).toMatchInlineSnapshot( - `"
number:
42
also:
with \\\\"quotes\\\\" or 'single qoutes'
foo:
bar
hello:
<h1>World</h1>
_id:
a
_type:
doc
_score:
1
"` - ); + expect(formatRow({ ...hit, highlight: { number: '42' } }, indexPattern)).toMatchInlineSnapshot(` + + `); }); it('formats top level objects using formatter', () => { @@ -111,10 +185,19 @@ describe('Row formatter', () => { getByName: jest.fn(), }, indexPattern - ).trim() - ).toMatchInlineSnapshot( - `"
object.value:
formatted, formatted
"` - ); + ) + ).toMatchInlineSnapshot(` + + `); }); it('formats top level objects in alphabetical order', () => { @@ -124,11 +207,13 @@ describe('Row formatter', () => { indexPattern.getFormatterForField = jest.fn().mockReturnValue({ convert: () => 'formatted', }); - const formatted = formatTopLevelObject( - { fields: { 'a.zzz': [100], 'a.ccc': [50] } }, - { 'a.zzz': [100], 'a.ccc': [50], getByName: jest.fn() }, - indexPattern - ).trim(); + const formatted = ReactDOM.renderToStaticMarkup( + formatTopLevelObject( + { fields: { 'a.zzz': [100], 'a.ccc': [50] } }, + { 'a.zzz': [100], 'a.ccc': [50], getByName: jest.fn() }, + indexPattern + ) + ); expect(formatted.indexOf('
a.ccc:
')).toBeLessThan(formatted.indexOf('
a.zzz:
')); }); @@ -156,10 +241,23 @@ describe('Row formatter', () => { getByName: jest.fn(), }, indexPattern - ).trim() - ).toMatchInlineSnapshot( - `"
object.keys:
formatted, formatted
object.value:
formatted, formatted
"` - ); + ) + ).toMatchInlineSnapshot(` + + `); }); it('formats top level objects, converting unknown fields to string', () => { @@ -177,9 +275,18 @@ describe('Row formatter', () => { getByName: jest.fn(), }, indexPattern - ).trim() - ).toMatchInlineSnapshot( - `"
object.value:
5, 10
"` - ); + ) + ).toMatchInlineSnapshot(` + + `); }); }); diff --git a/src/plugins/discover/public/application/angular/helpers/row_formatter.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx similarity index 87% rename from src/plugins/discover/public/application/angular/helpers/row_formatter.tsx rename to src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx index c410273cc7510..51e83f78f9f1c 100644 --- a/src/plugins/discover/public/application/angular/helpers/row_formatter.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/row_formatter.tsx @@ -7,9 +7,8 @@ */ import React, { Fragment } from 'react'; -import ReactDOM from 'react-dom/server'; -import { MAX_DOC_FIELDS_DISPLAYED } from '../../../../common'; -import { getServices, IndexPattern } from '../../../kibana_services'; +import { MAX_DOC_FIELDS_DISPLAYED } from '../../../../../../../common'; +import { getServices, IndexPattern } from '../../../../../../kibana_services'; interface Props { defPairs: Array<[string, unknown]>; @@ -44,9 +43,7 @@ export const formatRow = (hit: Record, indexPattern: IndexPattern) pairs.push([displayKey ? displayKey : key, val]); }); const maxEntries = getServices().uiSettings.get(MAX_DOC_FIELDS_DISPLAYED); - return ReactDOM.renderToStaticMarkup( - - ); + return ; }; export const formatTopLevelObject = ( @@ -80,7 +77,5 @@ export const formatTopLevelObject = ( pairs.push([displayKey ? displayKey : key, formatted]); }); const maxEntries = getServices().uiSettings.get(MAX_DOC_FIELDS_DISPLAYED); - return ReactDOM.renderToStaticMarkup( - - ); + return ; }; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts new file mode 100644 index 0000000000000..5522e3c150213 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/lib/use_pager.ts @@ -0,0 +1,78 @@ +/* + * 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 { useCallback, useEffect, useState } from 'react'; + +interface MetaParams { + currentPage: number; + totalItems: number; + totalPages: number; + startIndex: number; + hasNextPage: boolean; + pageSize: number; +} + +interface ProvidedMeta { + updatedPageSize?: number; + updatedCurrentPage?: number; +} + +const INITIAL_PAGE_SIZE = 50; + +export const usePager = ({ totalItems }: { totalItems: number }) => { + const [meta, setMeta] = useState({ + currentPage: 0, + totalItems, + startIndex: 0, + totalPages: Math.ceil(totalItems / INITIAL_PAGE_SIZE), + hasNextPage: true, + pageSize: INITIAL_PAGE_SIZE, + }); + + const getNewMeta = useCallback( + (newMeta: ProvidedMeta) => { + const actualCurrentPage = newMeta.updatedCurrentPage ?? meta.currentPage; + const actualPageSize = newMeta.updatedPageSize ?? meta.pageSize; + + const newTotalPages = Math.ceil(totalItems / actualPageSize); + const newStartIndex = actualPageSize * actualCurrentPage; + + return { + currentPage: actualCurrentPage, + totalPages: newTotalPages, + startIndex: newStartIndex, + totalItems, + hasNextPage: meta.currentPage + 1 < meta.totalPages, + pageSize: actualPageSize, + }; + }, + [meta.currentPage, meta.pageSize, meta.totalPages, totalItems] + ); + + const onPageChange = useCallback( + (pageIndex: number) => setMeta(getNewMeta({ updatedCurrentPage: pageIndex })), + [getNewMeta] + ); + + const onPageSizeChange = useCallback( + (newPageSize: number) => + setMeta(getNewMeta({ updatedPageSize: newPageSize, updatedCurrentPage: 0 })), + [getNewMeta] + ); + + /** + * Update meta on totalItems change + */ + useEffect(() => setMeta(getNewMeta({})), [getNewMeta, totalItems]); + + return { + ...meta, + onPageChange, + onPageSizeChange, + }; +}; diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.test.tsx index 1136b693c9e74..e5212e877e8ba 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.test.tsx @@ -36,7 +36,6 @@ function getProps(fetchStatus: FetchStatus, hits: ElasticSearchHit[]) { return { expandedDoc: undefined, indexPattern: indexPatternMock, - isMobile: jest.fn(() => false), onAddFilter: jest.fn(), savedSearch: savedSearchMock, documents$, diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx index 13cf021ff2573..e0e0c9c6f8831 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx @@ -5,11 +5,15 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useRef, useMemo, useCallback, memo } from 'react'; -import { EuiFlexItem, EuiSpacer, EuiText, EuiLoadingSpinner } from '@elastic/eui'; +import React, { useMemo, useCallback, memo } from 'react'; +import { + EuiFlexItem, + EuiSpacer, + EuiText, + EuiLoadingSpinner, + EuiScreenReaderOnly, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { DocTableLegacy } from '../../../../angular/doc_table/create_doc_table_react'; -import { SortPairArr } from '../../../../angular/doc_table/lib/get_sort'; import { DocViewFilterFn, ElasticSearchHit } from '../../../../doc_views/doc_views_types'; import { DiscoverGrid } from '../../../../components/discover_grid/discover_grid'; import { FetchStatus } from '../../../../types'; @@ -26,15 +30,16 @@ import { DataDocumentsMsg, DataDocuments$ } from '../../services/use_saved_searc import { DiscoverServices } from '../../../../../build_services'; import { AppState, GetStateReturn } from '../../services/discover_state'; import { useDataState } from '../../utils/use_data_state'; +import { DocTableInfinite } from '../doc_table/doc_table_infinite'; +import { SortPairArr } from '../doc_table/lib/get_sort'; -const DocTableLegacyMemoized = React.memo(DocTableLegacy); +const DocTableInfiniteMemoized = React.memo(DocTableInfinite); const DataGridMemoized = React.memo(DiscoverGrid); function DiscoverDocumentsComponent({ documents$, expandedDoc, indexPattern, - isMobile, onAddFilter, savedSearch, services, @@ -45,7 +50,6 @@ function DiscoverDocumentsComponent({ documents$: DataDocuments$; expandedDoc?: ElasticSearchHit; indexPattern: IndexPattern; - isMobile: () => boolean; navigateTo: (url: string) => void; onAddFilter: DocViewFilterFn; savedSearch: SavedSearch; @@ -57,11 +61,11 @@ function DiscoverDocumentsComponent({ const { capabilities, indexPatterns, uiSettings } = services; const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); - const scrollableDesktop = useRef(null); const isLegacy = useMemo(() => uiSettings.get(DOC_TABLE_LEGACY), [uiSettings]); const sampleSize = useMemo(() => uiSettings.get(SAMPLE_SIZE_SETTING), [uiSettings]); const documentState: DataDocumentsMsg = useDataState(documents$); + const isLoading = documentState.fetchStatus === FetchStatus.LOADING; const rows = useMemo(() => documentState.result || [], [documentState.result]); @@ -75,21 +79,6 @@ function DiscoverDocumentsComponent({ useNewFieldsApi, }); - /** - * Legacy function, remove once legacy grid is removed - */ - const onBackToTop = useCallback(() => { - if (scrollableDesktop && scrollableDesktop.current) { - scrollableDesktop.current.focus(); - } - // Only the desktop one needs to target a specific container - if (!isMobile() && scrollableDesktop.current) { - scrollableDesktop.current.scrollTo(0, 0); - } else if (window) { - window.scrollTo(0, 0); - } - }, [scrollableDesktop, isMobile]); - const onResize = useCallback( (colSettings: { columnId: string; width: number }) => { const grid = { ...state.grid } || {}; @@ -131,62 +120,57 @@ function DiscoverDocumentsComponent({ } return ( - -
-

+ + +

- {isLegacy && rows && rows.length && ( - + {isLegacy && rows && rows.length && ( + + )} + {!isLegacy && ( +
+ - )} - {!isLegacy && ( -
- -
- )} -

+
+ )} ); } diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss index 485bdc65c6cb6..2401325dd76f2 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.scss @@ -84,17 +84,8 @@ discover-app { } .dscTable { - // SASSTODO: add a monospace modifier to the doc-table component - .kbnDocTable__row { - font-family: $euiCodeFontFamily; - font-size: $euiFontSizeXS; - } -} - -.dscTable__footer { - background-color: $euiColorLightShade; - padding: $euiSizeXS $euiSizeS; - text-align: center; + // needs for scroll container of lagacy table + min-height: 0; } .dscDocuments__loading { diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx index 8930722e813ce..94e28c3f1d54c 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import './discover_layout.scss'; -import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react'; +import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react'; import { EuiSpacer, EuiButtonIcon, @@ -66,13 +66,11 @@ export function DiscoverLayout({ stateContainer, }: DiscoverLayoutProps) { const { trackUiMetric, capabilities, indexPatterns, data, uiSettings, filterManager } = services; + const { main$, charts$, totalHits$ } = savedSearchData$; const [expandedDoc, setExpandedDoc] = useState(undefined); const [inspectorSession, setInspectorSession] = useState(undefined); - const collapseIcon = useRef(null); const fetchCounter = useRef(0); - const { main$, charts$, totalHits$ } = savedSearchData$; - const dataState: DataMainMsg = useDataState(main$); useEffect(() => { @@ -81,8 +79,6 @@ export function DiscoverLayout({ } }, [dataState.fetchStatus]); - // collapse icon isn't displayed in mobile view, use it to detect which view is displayed - const isMobile = useCallback(() => collapseIcon && !collapseIcon.current, []); const timeField = useMemo(() => { return indexPatternsUtils.isDefault(indexPattern) ? indexPattern.timeFieldName : undefined; }, [indexPattern]); @@ -208,7 +204,6 @@ export function DiscoverLayout({ aria-label={i18n.translate('discover.toggleSidebarAriaLabel', { defaultMessage: 'Toggle sidebar', })} - buttonRef={collapseIcon} /> @@ -261,11 +256,11 @@ export function DiscoverLayout({ /> + { + return ( + + {totalHitCount}, + }} + /> + + ); +}; diff --git a/src/plugins/discover/public/application/apps/main/services/use_discover_state.ts b/src/plugins/discover/public/application/apps/main/services/use_discover_state.ts index 1bf9710def03c..a5a064a8fc1c6 100644 --- a/src/plugins/discover/public/application/apps/main/services/use_discover_state.ts +++ b/src/plugins/discover/public/application/apps/main/services/use_discover_state.ts @@ -24,7 +24,7 @@ import { import { useSearchSession } from './use_search_session'; import { FetchStatus } from '../../../types'; import { getSwitchIndexPatternAppState } from '../utils/get_switch_index_pattern_app_state'; -import { SortPairArr } from '../../../angular/doc_table/lib/get_sort'; +import { SortPairArr } from '../components/doc_table/lib/get_sort'; export function useDiscoverState({ services, diff --git a/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts b/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts index 00473956c57e3..225d90c61de12 100644 --- a/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts +++ b/src/plugins/discover/public/application/apps/main/utils/get_sharing_data.ts @@ -10,8 +10,8 @@ import type { Capabilities, IUiSettingsClient } from 'kibana/public'; import { ISearchSource } from '../../../../../../data/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; import type { SavedSearch, SortOrder } from '../../../../saved_searches/types'; +import { getSortForSearchSource } from '../components/doc_table'; import { AppState } from '../services/discover_state'; -import { getSortForSearchSource } from '../../../angular/doc_table'; /** * Preparing data to share the current state as link or CSV/Report diff --git a/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts b/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts index 426272fa8ce1c..fc835d4d3dd16 100644 --- a/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts +++ b/src/plugins/discover/public/application/apps/main/utils/get_state_defaults.ts @@ -9,12 +9,11 @@ import { cloneDeep } from 'lodash'; import { IUiSettingsClient } from 'kibana/public'; import { DEFAULT_COLUMNS_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; -import { getSortArray } from '../../../angular/doc_table'; -import { getDefaultSort } from '../../../angular/doc_table/lib/get_default_sort'; import { SavedSearch } from '../../../../saved_searches'; import { DataPublicPluginStart } from '../../../../../../data/public'; import { AppState } from '../services/discover_state'; +import { getDefaultSort, getSortArray } from '../components/doc_table'; function getDefaultColumns(savedSearch: SavedSearch, config: IUiSettingsClient) { if (savedSearch.columns && savedSearch.columns.length > 0) { diff --git a/src/plugins/discover/public/application/apps/main/utils/get_switch_index_pattern_app_state.ts b/src/plugins/discover/public/application/apps/main/utils/get_switch_index_pattern_app_state.ts index f7154b26c7ed6..00f194662e410 100644 --- a/src/plugins/discover/public/application/apps/main/utils/get_switch_index_pattern_app_state.ts +++ b/src/plugins/discover/public/application/apps/main/utils/get_switch_index_pattern_app_state.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import { getSortArray } from '../../../angular/doc_table'; -import { SortPairArr } from '../../../angular/doc_table/lib/get_sort'; import { IndexPattern } from '../../../../kibana_services'; +import { getSortArray, SortPairArr } from '../components/doc_table/lib/get_sort'; /** * Helper function to remove or adapt the currently selected columns/sort to be valid with the next diff --git a/src/plugins/discover/public/application/apps/main/utils/update_search_source.ts b/src/plugins/discover/public/application/apps/main/utils/update_search_source.ts index 3fac75a198d53..b4a1dab41a096 100644 --- a/src/plugins/discover/public/application/apps/main/utils/update_search_source.ts +++ b/src/plugins/discover/public/application/apps/main/utils/update_search_source.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { getSortForSearchSource } from '../../../angular/doc_table'; import { SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; import { IndexPattern, ISearchSource } from '../../../../../../data/common'; import { SortOrder } from '../../../../saved_searches/types'; import { DiscoverServices } from '../../../../build_services'; import { indexPatterns as indexPatternsUtils } from '../../../../../../data/public'; +import { getSortForSearchSource } from '../components/doc_table'; /** * Helper function to update the given searchSource before fetching/sharing/persisting diff --git a/src/plugins/discover/public/application/components/context_app/context_app.tsx b/src/plugins/discover/public/application/components/context_app/context_app.tsx index c52f22c60bb5b..37963eb2dfa93 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app.tsx @@ -25,6 +25,7 @@ import { useContextAppFetch } from './use_context_app_fetch'; import { popularizeField } from '../../helpers/popularize_field'; import { ContextAppContent } from './context_app_content'; import { SurrDocType } from '../../angular/context/api/context'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; const ContextAppContentMemoized = memo(ContextAppContent); @@ -161,7 +162,7 @@ export const ContextApp = ({ indexPattern, indexPatternId, anchorId }: ContextAp predecessorCount={appState.predecessorCount} successorCount={appState.successorCount} setAppState={setAppState} - addFilter={addFilter} + addFilter={addFilter as DocViewFilterFn} rows={rows} predecessors={fetchedState.predecessors} successors={fetchedState.successors} diff --git a/src/plugins/discover/public/application/components/context_app/context_app_content.test.tsx b/src/plugins/discover/public/application/components/context_app/context_app_content.test.tsx index 0536ab7e6a025..1b95af8bdbe1c 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_content.test.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_content.test.tsx @@ -8,73 +8,76 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test/jest'; -import { uiSettingsMock as mockUiSettings } from '../../../__mocks__/ui_settings'; -import { DocTableLegacy } from '../../angular/doc_table/create_doc_table_react'; import { findTestSubject } from '@elastic/eui/lib/test'; import { ActionBar } from '../../angular/context/components/action_bar/action_bar'; import { AppState, GetStateReturn } from '../../angular/context_state'; import { SortDirection } from 'src/plugins/data/common'; import { EsHitRecordList } from '../../angular/context/api/context'; import { ContextAppContent, ContextAppContentProps } from './context_app_content'; -import { getServices } from '../../../kibana_services'; +import { getServices, setServices } from '../../../kibana_services'; import { LoadingStatus } from '../../angular/context_query_state'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; import { DiscoverGrid } from '../discover_grid/discover_grid'; - -jest.mock('../../../kibana_services', () => { - return { - getServices: () => ({ - uiSettings: mockUiSettings, - }), - }; -}); +import { discoverServiceMock } from '../../../__mocks__/services'; +import { DocTableWrapper } from '../../apps/main/components/doc_table/doc_table_wrapper'; describe('ContextAppContent test', () => { - const hit = { - _id: '123', - _index: 'test_index', - _score: null, - _version: 1, - _source: { - category: ["Men's Clothing"], - currency: 'EUR', - customer_first_name: 'Walker', - customer_full_name: 'Walker Texas Ranger', - customer_gender: 'MALE', - customer_last_name: 'Ranger', - }, - fields: [{ order_date: ['2020-10-19T13:35:02.000Z'] }], - sort: [1603114502000, 2092], - }; - const defaultProps = ({ - columns: ['Time (@timestamp)', '_source'], - indexPattern: indexPatternMock, - appState: ({} as unknown) as AppState, - stateContainer: ({} as unknown) as GetStateReturn, - anchorStatus: LoadingStatus.LOADED, - predecessorsStatus: LoadingStatus.LOADED, - successorsStatus: LoadingStatus.LOADED, - rows: ([hit] as unknown) as EsHitRecordList, - predecessors: [], - successors: [], - defaultStepSize: 5, - predecessorCount: 10, - successorCount: 10, - useNewFieldsApi: false, - isPaginationEnabled: false, - onAddColumn: () => {}, - onRemoveColumn: () => {}, - onSetColumns: () => {}, - services: getServices(), - sort: [['order_date', 'desc']] as Array<[string, SortDirection]>, - isLegacy: true, - setAppState: () => {}, - addFilter: () => {}, - } as unknown) as ContextAppContentProps; + let hit; + let defaultProps: ContextAppContentProps; + + beforeEach(() => { + setServices(discoverServiceMock); + + hit = { + _id: '123', + _index: 'test_index', + _score: null, + _version: 1, + fields: [ + { + order_date: ['2020-10-19T13:35:02.000Z'], + }, + ], + _source: { + category: ["Men's Clothing"], + currency: 'EUR', + customer_first_name: 'Walker', + customer_full_name: 'Walker Texas Ranger', + customer_gender: 'MALE', + customer_last_name: 'Ranger', + }, + sort: [1603114502000, 2092], + }; + defaultProps = ({ + columns: ['order_date', '_source'], + indexPattern: indexPatternMock, + appState: ({} as unknown) as AppState, + stateContainer: ({} as unknown) as GetStateReturn, + anchorStatus: LoadingStatus.LOADED, + predecessorsStatus: LoadingStatus.LOADED, + successorsStatus: LoadingStatus.LOADED, + rows: ([hit] as unknown) as EsHitRecordList, + predecessors: [], + successors: [], + defaultStepSize: 5, + predecessorCount: 10, + successorCount: 10, + useNewFieldsApi: true, + isPaginationEnabled: false, + onAddColumn: () => {}, + onRemoveColumn: () => {}, + onSetColumns: () => {}, + services: getServices(), + sort: [['order_date', 'desc']] as Array<[string, SortDirection]>, + isLegacy: true, + setAppState: () => {}, + addFilter: () => {}, + } as unknown) as ContextAppContentProps; + }); it('should render legacy table correctly', () => { const component = mountWithIntl(); - expect(component.find(DocTableLegacy).length).toBe(1); + expect(component.find(DocTableWrapper).length).toBe(1); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); expect(loadingIndicator.length).toBe(0); expect(component.find(ActionBar).length).toBe(2); @@ -84,18 +87,11 @@ describe('ContextAppContent test', () => { const props = { ...defaultProps }; props.anchorStatus = LoadingStatus.LOADING; const component = mountWithIntl(); - expect(component.find(DocTableLegacy).length).toBe(0); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); + expect(component.find(DocTableWrapper).length).toBe(1); expect(loadingIndicator.length).toBe(1); }); - it('renders error message', () => { - const props = { ...defaultProps }; - props.anchorStatus = LoadingStatus.FAILED; - const component = mountWithIntl(); - expect(component.find(DocTableLegacy).length).toBe(0); - }); - it('should render discover grid correctly', () => { const props = { ...defaultProps, isLegacy: false }; const component = mountWithIntl(); diff --git a/src/plugins/discover/public/application/components/context_app/context_app_content.tsx b/src/plugins/discover/public/application/components/context_app/context_app_content.tsx index 4d7ce2aa52092..78c354cbf908d 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_content.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_content.tsx @@ -10,20 +10,17 @@ import React, { useState, Fragment, useMemo, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiHorizontalRule, EuiText } from '@elastic/eui'; import { CONTEXT_STEP_SETTING, DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../common'; -import { IndexPattern, IndexPatternField } from '../../../../../data/common'; +import { IndexPattern } from '../../../../../data/common'; import { SortDirection } from '../../../../../data/public'; -import { - DocTableLegacy, - DocTableLegacyProps, -} from '../../angular/doc_table/create_doc_table_react'; import { LoadingStatus } from '../../angular/context_query_state'; import { ActionBar } from '../../angular/context/components/action_bar/action_bar'; -import { DiscoverGrid, DiscoverGridProps } from '../discover_grid/discover_grid'; -import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { DiscoverGrid } from '../discover_grid/discover_grid'; +import { DocViewFilterFn, ElasticSearchHit } from '../../doc_views/doc_views_types'; import { AppState } from '../../angular/context_state'; -import { EsHitRecord, EsHitRecordList, SurrDocType } from '../../angular/context/api/context'; +import { EsHitRecordList, SurrDocType } from '../../angular/context/api/context'; import { DiscoverServices } from '../../../build_services'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from './utils/constants'; +import { DocTableContext } from '../../apps/main/components/doc_table/doc_table_context'; export interface ContextAppContentProps { columns: string[]; @@ -44,11 +41,7 @@ export interface ContextAppContentProps { useNewFieldsApi: boolean; isLegacy: boolean; setAppState: (newState: Partial) => void; - addFilter: ( - field: IndexPatternField | string, - values: unknown, - operation: string - ) => Promise; + addFilter: DocViewFilterFn; } const controlColumnIds = ['openDetails']; @@ -57,8 +50,8 @@ export function clamp(value: number) { return Math.max(Math.min(MAX_CONTEXT_SIZE, value), MIN_CONTEXT_SIZE); } -const DataGridMemoized = React.memo(DiscoverGrid); -const DocTableLegacyMemoized = React.memo(DocTableLegacy); +const DiscoverGridMemoized = React.memo(DiscoverGrid); +const DocTableContextMemoized = React.memo(DocTableContext); const ActionBarMemoized = React.memo(ActionBar); export function ContextAppContent({ @@ -84,8 +77,7 @@ export function ContextAppContent({ }: ContextAppContentProps) { const { uiSettings: config } = services; - const [expandedDoc, setExpandedDoc] = useState(undefined); - const isAnchorLoaded = anchorStatus === LoadingStatus.LOADED; + const [expandedDoc, setExpandedDoc] = useState(); const isAnchorLoading = anchorStatus === LoadingStatus.LOADING || anchorStatus === LoadingStatus.UNINITIALIZED; const arePredecessorsLoading = @@ -100,50 +92,8 @@ export function ContextAppContent({ ); const defaultStepSize = useMemo(() => parseInt(config.get(CONTEXT_STEP_SETTING), 10), [config]); - const docTableProps = () => { - return { - ariaLabelledBy: 'surDocumentsAriaLabel', - columns, - rows: rows as ElasticSearchHit[], - indexPattern, - expandedDoc, - isLoading: isAnchorLoading, - sampleSize: 0, - sort: sort as [[string, SortDirection]], - isSortEnabled: false, - showTimeCol, - services, - useNewFieldsApi, - isPaginationEnabled: false, - controlColumnIds, - setExpandedDoc, - onFilter: addFilter, - onAddColumn, - onRemoveColumn, - onSetColumns, - } as DiscoverGridProps; - }; - - const legacyDocTableProps = () => { - // @ts-expect-error doesn't implement full DocTableLegacyProps interface - return { - columns, - indexPattern, - minimumVisibleRows: rows.length, - rows, - onFilter: addFilter, - onAddColumn, - onRemoveColumn, - sort, - useNewFieldsApi, - } as DocTableLegacyProps; - }; - const loadingFeedback = () => { - if ( - isLegacy && - (anchorStatus === LoadingStatus.UNINITIALIZED || anchorStatus === LoadingStatus.LOADING) - ) { + if (isLegacy && isAnchorLoading) { return ( @@ -170,18 +120,47 @@ export function ContextAppContent({ docCountAvailable={predecessors.length} onChangeCount={onChangeCount} isLoading={arePredecessorsLoading} - isDisabled={!isAnchorLoaded} + isDisabled={isAnchorLoading} /> {loadingFeedback()} - {isLegacy && isAnchorLoaded && ( -
- -
+ {isLegacy && rows && rows.length !== 0 && ( + )} - {!isLegacy && ( + {!isLegacy && rows && rows.length && (
- +
)} @@ -192,7 +171,7 @@ export function ContextAppContent({ docCountAvailable={successors.length} onChangeCount={onChangeCount} isLoading={areSuccessorsLoading} - isDisabled={!isAnchorLoaded} + isDisabled={isAnchorLoading} /> ); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index f1c56b7a57195..c727e7784cca6 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -28,7 +28,6 @@ import { DiscoverGridFlyout } from './discover_grid_flyout'; import { DiscoverGridContext } from './discover_grid_context'; import { getRenderCellValueFn } from './get_render_cell_value'; import { DiscoverGridSettings } from './types'; -import { SortPairArr } from '../../angular/doc_table/lib/get_sort'; import { getEuiGridColumns, getLeadControlColumns, @@ -40,6 +39,7 @@ import { getDisplayedColumns } from '../../helpers/columns'; import { KibanaContextProvider } from '../../../../../kibana_react/public'; import { MAX_DOC_FIELDS_DISPLAYED } from '../../../../common'; import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_document_selection'; +import { SortPairArr } from '../../apps/main/components/doc_table/lib/get_sort'; interface SortObj { id: string; @@ -369,7 +369,7 @@ export const DiscoverGrid = ({ > {i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkTextSimple', { diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss index 12d56d564b855..e845ba7238303 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss @@ -45,7 +45,6 @@ } .kbnDocViewer__value { - display: inline-block; word-break: break-all; word-wrap: break-word; white-space: pre-wrap; diff --git a/src/plugins/discover/public/application/components/table/table_helper.test.ts b/src/plugins/discover/public/application/components/table/table_helper.test.ts deleted file mode 100644 index 738556aaea085..0000000000000 --- a/src/plugins/discover/public/application/components/table/table_helper.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { arrayContainsObjects } from './table_helper'; - -describe('arrayContainsObjects', () => { - it(`returns false for an array of primitives`, () => { - const actual = arrayContainsObjects(['test', 'test']); - expect(actual).toBeFalsy(); - }); - - it(`returns true for an array of objects`, () => { - const actual = arrayContainsObjects([{}, {}]); - expect(actual).toBeTruthy(); - }); - - it(`returns true for an array of objects and primitves`, () => { - const actual = arrayContainsObjects([{}, 'sdf']); - expect(actual).toBeTruthy(); - }); - - it(`returns false for an array of null values`, () => { - const actual = arrayContainsObjects([null, null]); - expect(actual).toBeFalsy(); - }); - - it(`returns false if no array is given`, () => { - const actual = arrayContainsObjects([null, null]); - expect(actual).toBeFalsy(); - }); -}); diff --git a/src/plugins/discover/public/application/components/table/table_helper.tsx b/src/plugins/discover/public/application/components/table/table_helper.tsx index 6af349af11f1d..e1c3de8d87c34 100644 --- a/src/plugins/discover/public/application/components/table/table_helper.tsx +++ b/src/plugins/discover/public/application/components/table/table_helper.tsx @@ -6,13 +6,6 @@ * Side Public License, v 1. */ -/** - * Returns true if the given array contains at least 1 object - */ -export function arrayContainsObjects(value: unknown[]): boolean { - return Array.isArray(value) && value.some((v) => typeof v === 'object' && v !== null); -} - /** * Removes markup added by kibana fields html formatter */ diff --git a/src/plugins/discover/public/application/doc_views/doc_views_types.ts b/src/plugins/discover/public/application/doc_views/doc_views_types.ts index 58399f31e032f..fe185d7c21f03 100644 --- a/src/plugins/discover/public/application/doc_views/doc_views_types.ts +++ b/src/plugins/discover/public/application/doc_views/doc_views_types.ts @@ -18,7 +18,7 @@ export interface AngularDirective { export type AngularScope = IScope; -export type ElasticSearchHit = estypes.SearchResponse['hits']['hits'][number]; +export type ElasticSearchHit = estypes.SearchHit; export interface FieldMapping { filterable?: boolean; diff --git a/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx index 098c7f55fbd9f..3fd7b2f50d319 100644 --- a/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx @@ -22,11 +22,10 @@ import { Query, TimeRange, Filter, - IndexPatternField, IndexPattern, ISearchSource, + IndexPatternField, } from '../../../../data/common'; -import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; import { ElasticSearchHit } from '../doc_views/doc_views_types'; import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component'; import { UiActionsStart } from '../../../../ui_actions/public'; @@ -38,23 +37,26 @@ import { SEARCH_FIELDS_FROM_SOURCE, SORT_DEFAULT_ORDER_SETTING, } from '../../../common'; -import * as columnActions from '../angular/doc_table/actions/columns'; -import { getSortForSearchSource, getDefaultSort } from '../angular/doc_table'; +import * as columnActions from '../apps/main/components/doc_table/actions/columns'; import { handleSourceColumnState } from '../angular/helpers'; import { DiscoverGridProps } from '../components/discover_grid/discover_grid'; import { DiscoverGridSettings } from '../components/discover_grid/types'; - -export interface SearchProps extends Partial { - settings?: DiscoverGridSettings; - description?: string; - sharedItemTitle?: string; - inspectorAdapters?: Adapters; - - filter?: (field: IndexPatternField, value: string[], operator: string) => void; - hits?: ElasticSearchHit[]; - totalHitCount?: number; - onMoveColumn?: (column: string, index: number) => void; -} +import { DocTableProps } from '../apps/main/components/doc_table/doc_table_wrapper'; +import { getDefaultSort, getSortForSearchSource } from '../apps/main/components/doc_table'; +import { SortOrder } from '../apps/main/components/doc_table/components/table_header/helpers'; + +export type SearchProps = Partial & + Partial & { + settings?: DiscoverGridSettings; + description?: string; + sharedItemTitle?: string; + inspectorAdapters?: Adapters; + + filter?: (field: IndexPatternField, value: string[], operator: string) => void; + hits?: ElasticSearchHit[]; + totalHitCount?: number; + onMoveColumn?: (column: string, index: number) => void; + }; interface SearchEmbeddableConfig { savedSearch: SavedSearch; diff --git a/src/plugins/discover/public/application/embeddable/saved_search_embeddable_component.tsx b/src/plugins/discover/public/application/embeddable/saved_search_embeddable_component.tsx index 5b2a2635d04bd..76b316d575cf2 100644 --- a/src/plugins/discover/public/application/embeddable/saved_search_embeddable_component.tsx +++ b/src/plugins/discover/public/application/embeddable/saved_search_embeddable_component.tsx @@ -8,9 +8,12 @@ import React from 'react'; -import { DiscoverGridEmbeddable } from '../angular/create_discover_grid_directive'; -import { DiscoverDocTableEmbeddable } from '../angular/doc_table/create_doc_table_embeddable'; -import { DiscoverGridProps } from '../components/discover_grid/discover_grid'; +import { + DiscoverGridEmbeddable, + DiscoverGridEmbeddableProps, +} from '../angular/create_discover_grid_directive'; +import { DiscoverDocTableEmbeddable } from '../apps/main/components/doc_table/create_doc_table_embeddable'; +import { DocTableEmbeddableProps } from '../apps/main/components/doc_table/doc_table_embeddable'; import { SearchProps } from './saved_search_embeddable'; interface SavedSearchEmbeddableComponentProps { @@ -32,8 +35,8 @@ export function SavedSearchEmbeddableComponent({ ...searchProps, refs, }; - return ; + return ; } - const discoverGridProps = searchProps as DiscoverGridProps; + const discoverGridProps = searchProps as DiscoverGridEmbeddableProps; return ; } diff --git a/src/plugins/discover/public/application/embeddable/types.ts b/src/plugins/discover/public/application/embeddable/types.ts index 642c65c4b2a55..5a08534918d4f 100644 --- a/src/plugins/discover/public/application/embeddable/types.ts +++ b/src/plugins/discover/public/application/embeddable/types.ts @@ -12,9 +12,9 @@ import { EmbeddableOutput, IEmbeddable, } from 'src/plugins/embeddable/public'; -import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; import { Filter, IndexPattern, TimeRange, Query } from '../../../../data/public'; import { SavedSearch } from '../..'; +import { SortOrder } from '../apps/main/components/doc_table/components/table_header/helpers'; export interface SearchInput extends EmbeddableInput { timeRange: TimeRange; diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.ts b/src/plugins/discover/public/application/helpers/get_single_doc_url.ts similarity index 65% rename from src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.ts rename to src/plugins/discover/public/application/helpers/get_single_doc_url.ts index 7eb31459eb4f5..913463e6d44a4 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_row/truncate_by_height.ts +++ b/src/plugins/discover/public/application/helpers/get_single_doc_url.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export const truncateByHeight = ({ body }: { body: string }) => { - return `
${body}
`; +export const getSingleDocUrl = (indexPatternId: string, rowIndex: string, rowId: string) => { + return `/app/discover#/doc/${indexPatternId}/${rowIndex}?id=${encodeURIComponent(rowId)}`; }; diff --git a/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts index 418cbf6eac9cd..8a28369d1f5f2 100644 --- a/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts @@ -18,7 +18,7 @@ import { AppState as ContextState, GetStateReturn as ContextGetStateReturn, } from '../angular/context_state'; -import { getStateColumnActions } from '../angular/doc_table/actions/columns'; +import { getStateColumnActions } from '../apps/main/components/doc_table/actions/columns'; interface UseDataGridColumnsProps { capabilities: Capabilities; diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 7d0228131cc67..09a162e051bf6 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -228,13 +228,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should make the document table scrollable', async function () { await PageObjects.discover.clearFieldSearchInput(); - const dscTable = await find.byCssSelector('.dscTable'); + const dscTableWrapper = await find.byCssSelector('.kbnDocTableWrapper'); const fieldNames = await PageObjects.discover.getAllFieldNames(); - const clientHeight = await dscTable.getAttribute('clientHeight'); + const clientHeight = await dscTableWrapper.getAttribute('clientHeight'); let fieldCounter = 0; const checkScrollable = async () => { - const scrollWidth = await dscTable.getAttribute('scrollWidth'); - const clientWidth = await dscTable.getAttribute('clientWidth'); + const scrollWidth = await dscTableWrapper.getAttribute('scrollWidth'); + const clientWidth = await dscTableWrapper.getAttribute('clientWidth'); log.debug(`scrollWidth: ${scrollWidth}, clientWidth: ${clientWidth}`); return Number(scrollWidth) > Number(clientWidth); }; @@ -251,7 +251,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return await checkScrollable(); }); // so now we need to check if the horizontal scrollbar is displayed - const newClientHeight = await dscTable.getAttribute('clientHeight'); + const newClientHeight = await dscTableWrapper.getAttribute('clientHeight'); expect(Number(clientHeight)).to.be.above(Number(newClientHeight)); }); }); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index ecba9549cea02..ea727069c927d 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -228,9 +228,13 @@ export class DashboardPageObject extends FtrService { */ public async expectToolbarPaginationDisplayed() { - const isLegacyDefault = this.discover.useLegacyTable(); + const isLegacyDefault = await this.discover.useLegacyTable(); if (isLegacyDefault) { - const subjects = ['btnPrevPage', 'btnNextPage', 'toolBarPagerText']; + const subjects = [ + 'pagination-button-previous', + 'pagination-button-next', + 'toolBarTotalDocsText', + ]; await Promise.all(subjects.map(async (subj) => await this.testSubjects.existOrFail(subj))); } else { const subjects = ['pagination-button-previous', 'pagination-button-next']; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1b20d91d480b2..0d4432d8dac56 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1523,9 +1523,6 @@ "discover.doc.somethingWentWrongDescriptionAddon": "インデックスが存在することを確認してください。", "discover.docTable.limitedSearchResultLabel": "{resultCount}件の結果のみが表示されます。検索結果を絞り込みます。", "discover.docTable.noResultsTitle": "結果が見つかりませんでした", - "discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel": "表内の次ページ", - "discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel": "表内の前ページ", - "discover.docTable.pagerControl.pagesCountLabel": "{startItem}–{endItem}/{totalItems}", "discover.docTable.tableHeader.documentHeader": "ドキュメント", "discover.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "{columnName}列を左に移動", "discover.docTable.tableHeader.moveColumnLeftButtonTooltip": "列を左に移動", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d3c89b74eca8b..fc8b27aa497cd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1532,9 +1532,6 @@ "discover.doc.somethingWentWrongDescriptionAddon": "请确保索引存在。", "discover.docTable.limitedSearchResultLabel": "仅限于 {resultCount} 个结果。优化您的搜索。", "discover.docTable.noResultsTitle": "找不到结果", - "discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel": "表中下一页", - "discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel": "表中上一页", - "discover.docTable.pagerControl.pagesCountLabel": "{startItem}–{endItem}/{totalItems}", "discover.docTable.tableHeader.documentHeader": "文档", "discover.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "向左移动“{columnName}”列", "discover.docTable.tableHeader.moveColumnLeftButtonTooltip": "向左移动列",