diff --git a/src/plugins/discover/public/__mocks__/ui_settings.ts b/src/plugins/discover/public/__mocks__/ui_settings.ts index e021a39a568e99..8347ff18edd7db 100644 --- a/src/plugins/discover/public/__mocks__/ui_settings.ts +++ b/src/plugins/discover/public/__mocks__/ui_settings.ts @@ -7,7 +7,7 @@ */ import { IUiSettingsClient } from 'kibana/public'; -import { DEFAULT_COLUMNS_SETTING, SAMPLE_SIZE_SETTING } from '../../common'; +import { DEFAULT_COLUMNS_SETTING, DOC_TABLE_LEGACY, SAMPLE_SIZE_SETTING } from '../../common'; export const uiSettingsMock = ({ get: (key: string) => { @@ -15,6 +15,8 @@ export const uiSettingsMock = ({ return 10; } else if (key === DEFAULT_COLUMNS_SETTING) { return ['default_column']; + } else if (key === DOC_TABLE_LEGACY) { + return true; } }, } as unknown) as IUiSettingsClient; diff --git a/src/plugins/discover/public/application/angular/context.html b/src/plugins/discover/public/application/angular/context.html index 2c8e9a2a5d6f0b..adafb3a62275ff 100644 --- a/src/plugins/discover/public/application/angular/context.html +++ b/src/plugins/discover/public/application/angular/context.html @@ -2,8 +2,9 @@ anchor-id="contextAppRoute.anchorId" columns="contextAppRoute.state.columns" index-pattern="contextAppRoute.indexPattern" + app-state="contextAppRoute.state" + state-container="contextAppRoute.stateContainer" filters="contextAppRoute.filters" predecessor-count="contextAppRoute.state.predecessorCount" successor-count="contextAppRoute.state.successorCount" - sort="contextAppRoute.state.sort" -> + sort="contextAppRoute.state.sort"> \ No newline at end of file diff --git a/src/plugins/discover/public/application/angular/context.js b/src/plugins/discover/public/application/angular/context.js index 01a28a5c174b6b..10c0fe9db19507 100644 --- a/src/plugins/discover/public/application/angular/context.js +++ b/src/plugins/discover/public/application/angular/context.js @@ -15,19 +15,12 @@ import { getState } from './context_state'; import contextAppRouteTemplate from './context.html'; import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; -const k7Breadcrumbs = ($route) => { - const { indexPattern } = $route.current.locals; - const { id } = $route.current.params; - +const k7Breadcrumbs = () => { return [ ...getRootBreadcrumbs(), { text: i18n.translate('discover.context.breadcrumb', { - defaultMessage: 'Context of {indexPatternTitle}#{docId}', - values: { - indexPatternTitle: indexPattern.title, - docId: id, - }, + defaultMessage: 'Surrounding documents', }), }, ]; @@ -51,6 +44,14 @@ getAngularModule().config(($routeProvider) => { function ContextAppRouteController($routeParams, $scope, $route) { const filterManager = getServices().filterManager; const indexPattern = $route.current.locals.indexPattern.ip; + const stateContainer = getState({ + defaultStepSize: getServices().uiSettings.get(CONTEXT_DEFAULT_SIZE_SETTING), + timeFieldName: indexPattern.timeFieldName, + storeInSessionStorage: getServices().uiSettings.get('state:storeInSessionStorage'), + history: getServices().history(), + toasts: getServices().core.notifications.toasts, + uiSettings: getServices().core.uiSettings, + }); const { startSync: startStateSync, stopSync: stopStateSync, @@ -59,14 +60,8 @@ function ContextAppRouteController($routeParams, $scope, $route) { setFilters, setAppState, flushToUrl, - } = getState({ - defaultStepSize: getServices().uiSettings.get(CONTEXT_DEFAULT_SIZE_SETTING), - timeFieldName: indexPattern.timeFieldName, - storeInSessionStorage: getServices().uiSettings.get('state:storeInSessionStorage'), - history: getServices().history(), - toasts: getServices().core.notifications.toasts, - uiSettings: getServices().core.uiSettings, - }); + } = stateContainer; + this.stateContainer = stateContainer; this.state = { ...appState.getState() }; this.anchorId = $routeParams.id; this.indexPattern = indexPattern; diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.test.ts b/src/plugins/discover/public/application/angular/context/api/anchor.test.ts index 62c9a2a5e3b908..4da8ddc7980036 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/anchor.test.ts @@ -8,22 +8,21 @@ import { EsQuerySortValue, SortDirection } from '../../../../../../data/public'; import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; -import { AnchorHitRecord, fetchAnchorProvider } from './anchor'; +import { fetchAnchorProvider } from './anchor'; +import { EsHitRecord, EsHitRecordList } from './context'; describe('context app', function () { let fetchAnchor: ( indexPatternId: string, anchorId: string, sort: EsQuerySortValue[] - ) => Promise; + ) => Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any let searchSourceStub: any; describe('function fetchAnchor', function () { beforeEach(() => { - searchSourceStub = createSearchSourceStub([ - { _id: 'hit1', fields: [], sort: [], _source: {} }, - ]); + searchSourceStub = createSearchSourceStub(([{ _id: 'hit1' }] as unknown) as EsHitRecordList); fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); }); @@ -139,16 +138,14 @@ describe('context app', function () { { _doc: SortDirection.desc }, ]).then((anchorDocument) => { expect(anchorDocument).toHaveProperty('property1', 'value1'); - expect(anchorDocument).toHaveProperty('$$_isAnchor', true); + expect(anchorDocument).toHaveProperty('isAnchor', true); }); }); }); describe('useNewFields API', () => { beforeEach(() => { - searchSourceStub = createSearchSourceStub([ - { _id: 'hit1', fields: [], sort: [], _source: {} }, - ]); + searchSourceStub = createSearchSourceStub(([{ _id: 'hit1' }] as unknown) as EsHitRecordList); fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub, true); }); diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.ts b/src/plugins/discover/public/application/angular/context/api/anchor.ts index da81ce525331ac..f2111d020aade8 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.ts +++ b/src/plugins/discover/public/application/angular/context/api/anchor.ts @@ -16,11 +16,6 @@ import { } from '../../../../../../data/public'; import { EsHitRecord } from './context'; -export interface AnchorHitRecord extends EsHitRecord { - // eslint-disable-next-line @typescript-eslint/naming-convention - $$_isAnchor: boolean; -} - export function fetchAnchorProvider( indexPatterns: IndexPatternsContract, searchSource: ISearchSource, @@ -30,7 +25,7 @@ export function fetchAnchorProvider( indexPatternId: string, anchorId: string, sort: EsQuerySortValue[] - ): Promise { + ): Promise { const indexPattern = await indexPatterns.get(indexPatternId); searchSource .setParent(undefined) @@ -66,8 +61,7 @@ export function fetchAnchorProvider( return { ...get(response, ['hits', 'hits', 0]), - // eslint-disable-next-line @typescript-eslint/naming-convention - $$_isAnchor: true, - } as AnchorHitRecord; + isAnchor: true, + } as EsHitRecord; }; } diff --git a/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts index dc097bc110e205..1acf57411c7959 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts @@ -11,7 +11,7 @@ import { get, last } from 'lodash'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; import { EsHitRecordList, fetchContextProvider } from './context'; import { setServices, SortDirection } from '../../../../kibana_services'; -import { AnchorHitRecord } from './anchor'; +import { EsHitRecord } from './context'; import { Query } from '../../../../../../data/public'; import { DiscoverServices } from '../../../../build_services'; @@ -75,7 +75,7 @@ describe('context app', function () { return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( 'predecessors', indexPatternId, - anchor as AnchorHitRecord, + anchor as EsHitRecord, timeField, tieBreakerField, sortDir, @@ -267,7 +267,7 @@ describe('context app', function () { return fetchContextProvider(createIndexPatternsStub(), true).fetchSurroundingDocs( 'predecessors', indexPatternId, - anchor as AnchorHitRecord, + anchor as EsHitRecord, timeField, tieBreakerField, sortDir, diff --git a/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts b/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts index f8fc7eb343206b..957a13e8daf093 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts @@ -13,7 +13,7 @@ import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs import { setServices, SortDirection } from '../../../../kibana_services'; import { Query } from '../../../../../../data/public'; import { EsHitRecordList, fetchContextProvider } from './context'; -import { AnchorHitRecord } from './anchor'; +import { EsHitRecord } from './context'; import { DiscoverServices } from '../../../../build_services'; const MS_PER_DAY = 24 * 60 * 60 * 1000; @@ -75,7 +75,7 @@ describe('context app', function () { return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( 'successors', indexPatternId, - anchor as AnchorHitRecord, + anchor as EsHitRecord, timeField, tieBreakerField, sortDir, @@ -270,7 +270,7 @@ describe('context app', function () { return fetchContextProvider(createIndexPatternsStub(), true).fetchSurroundingDocs( 'successors', indexPatternId, - anchor as AnchorHitRecord, + anchor as EsHitRecord, timeField, tieBreakerField, sortDir, 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 4309b9ca4c391b..cd81ca7b216b27 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { estypes } from '@elastic/elasticsearch'; import { Filter, IndexPatternsContract, IndexPattern } from 'src/plugins/data/public'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; @@ -14,17 +15,19 @@ import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; import { getServices } from '../../../../kibana_services'; -import { AnchorHitRecord } from './anchor'; export type SurrDocType = 'successors' | 'predecessors'; -export interface EsHitRecord { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fields: Record; - sort: number[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - _source: Record; - _id: string; -} +export type EsHitRecord = Required< + Pick< + estypes.SearchResponse['hits']['hits'][number], + '_id' | 'fields' | 'sort' | '_index' | '_version' + > +> & { + _source?: Record; + _score?: number; + isAnchor?: boolean; +}; + export type EsHitRecordList = EsHitRecord[]; const DAY_MILLIS = 24 * 60 * 60 * 1000; @@ -53,7 +56,7 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields async function fetchSurroundingDocs( type: SurrDocType, indexPatternId: string, - anchor: AnchorHitRecord, + anchor: EsHitRecord, timeField: string, tieBreakerField: string, sortDir: SortDirection, @@ -71,7 +74,7 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields const timeValueMillis = nanos !== '' ? convertIsoToMillis(anchor.fields[timeField][0]) : anchor.sort[0]; - const intervals = generateIntervals(LOOKUP_OFFSETS, timeValueMillis, type, sortDir); + const intervals = generateIntervals(LOOKUP_OFFSETS, timeValueMillis as number, type, sortDir); let documents: EsHitRecordList = []; for (const interval of intervals) { diff --git a/src/plugins/discover/public/application/angular/context/api/utils/get_es_query_search_after.ts b/src/plugins/discover/public/application/angular/context/api/utils/get_es_query_search_after.ts index fb0e58832a202e..c703abaf2e5237 100644 --- a/src/plugins/discover/public/application/angular/context/api/utils/get_es_query_search_after.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/get_es_query_search_after.ts @@ -28,23 +28,23 @@ export function getEsQuerySearchAfter( // already surrounding docs -> first or last record is used const afterTimeRecIdx = type === 'successors' && documents.length ? documents.length - 1 : 0; const afterTimeDoc = documents[afterTimeRecIdx]; - let afterTimeValue: string | number = afterTimeDoc.sort[0]; + let afterTimeValue = afterTimeDoc.sort[0] as string | number; if (nanoSeconds) { afterTimeValue = useNewFieldsApi - ? (afterTimeDoc.fields[timeFieldName] as Array)[0] - : (afterTimeDoc._source[timeFieldName] as string | number); + ? afterTimeDoc.fields[timeFieldName][0] + : afterTimeDoc._source?.[timeFieldName]; } - return [afterTimeValue, afterTimeDoc.sort[1]]; + return [afterTimeValue, afterTimeDoc.sort[1] as string | number]; } // if data_nanos adapt timestamp value for sorting, since numeric value was rounded by browser // ES search_after also works when number is provided as string const searchAfter = new Array(2) as EsQuerySearchAfter; - searchAfter[0] = anchor.sort[0]; + searchAfter[0] = anchor.sort[0] as string | number; if (nanoSeconds) { searchAfter[0] = useNewFieldsApi - ? (anchor.fields[timeFieldName] as Array)[0] - : (anchor._source[timeFieldName] as string | number); + ? anchor.fields[timeFieldName][0] + : anchor._source?.[timeFieldName]; } - searchAfter[1] = anchor.sort[1]; + searchAfter[1] = anchor.sort[1] as string | number; return searchAfter; } diff --git a/src/plugins/discover/public/application/angular/context/query/actions.tsx b/src/plugins/discover/public/application/angular/context/query/actions.tsx index 52c56d379d2591..f79c28bf6a1206 100644 --- a/src/plugins/discover/public/application/angular/context/query/actions.tsx +++ b/src/plugins/discover/public/application/angular/context/query/actions.tsx @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { getServices } from '../../../../kibana_services'; import { SEARCH_FIELDS_FROM_SOURCE } from '../../../../../common'; import { MarkdownSimple, toMountPoint } from '../../../../../../kibana_react/public'; -import { AnchorHitRecord, fetchAnchorProvider } from '../api/anchor'; +import { fetchAnchorProvider } from '../api/anchor'; import { EsHitRecord, EsHitRecordList, fetchContextProvider, SurrDocType } from '../api/context'; import { getQueryParameterActions } from '../query_parameters'; import { @@ -77,11 +77,12 @@ export function QueryActionsProvider(Promise: DiscoverPromise) { } setLoadingStatus(state)('anchor'); + const [[, sortDir]] = sort; return Promise.try(() => - fetchAnchor(indexPatternId, anchorId, [fromPairs([sort]), { [tieBreakerField]: sort[1] }]) + fetchAnchor(indexPatternId, anchorId, [fromPairs(sort), { [tieBreakerField]: sortDir }]) ).then( - (anchorDocument: AnchorHitRecord) => { + (anchorDocument: EsHitRecord) => { setLoadedStatus(state)('anchor'); state.rows.anchor = anchorDocument; return anchorDocument; @@ -120,7 +121,7 @@ export function QueryActionsProvider(Promise: DiscoverPromise) { } setLoadingStatus(state)(type); - const [sortField, sortDir] = sort; + const [[sortField, sortDir]] = sort; return Promise.try(() => fetchSurroundingDocs( diff --git a/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts b/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts index b54f11e9e67062..fac3e1ea6fad6e 100644 --- a/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts +++ b/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts @@ -10,6 +10,7 @@ import { getQueryParameterActions } from './actions'; import { FilterManager, SortDirection } from '../../../../../../data/public'; import { coreMock } from '../../../../../../../core/public/mocks'; import { ContextAppState, LoadingStatus, QueryParameters } from '../../context_app_state'; +import { EsHitRecord } from '../api/context'; const setupMock = coreMock.createSetup(); let state: ContextAppState; @@ -29,7 +30,7 @@ beforeEach(() => { anchorId: '', columns: [], filters: [], - sort: ['field', SortDirection.asc], + sort: [['field', SortDirection.asc]], tieBreakerField: '', }, loadingStatus: { @@ -39,8 +40,7 @@ beforeEach(() => { }, rows: { all: [], - // eslint-disable-next-line @typescript-eslint/naming-convention - anchor: { $$_isAnchor: true, fields: [], sort: [], _source: [], _id: '' }, + anchor: ({ isAnchor: true, fields: [], sort: [], _id: '' } as unknown) as EsHitRecord, predecessors: [], successors: [], }, @@ -129,7 +129,7 @@ describe('context query_parameter actions', function () { indexPatternId: 'INDEX_PATTERN', predecessorCount: 100, successorCount: 100, - sort: ['field', SortDirection.asc], + sort: [['field', SortDirection.asc]], tieBreakerField: '', }); @@ -142,7 +142,7 @@ describe('context query_parameter actions', function () { indexPatternId: 'INDEX_PATTERN', predecessorCount: 100, successorCount: 100, - sort: ['field', SortDirection.asc], + sort: [['field', SortDirection.asc]], tieBreakerField: '', }); }); diff --git a/src/plugins/discover/public/application/angular/context_app.html b/src/plugins/discover/public/application/angular/context_app.html index 3d731459ad8d72..21aad2688d2a3c 100644 --- a/src/plugins/discover/public/application/angular/context_app.html +++ b/src/plugins/discover/public/application/angular/context_app.html @@ -3,11 +3,14 @@ filter="contextApp.actions.addFilter" hits="contextApp.state.rows.all" index-pattern="contextApp.indexPattern" + app-state="contextApp.appState" + state-container="contextApp.stateContainer" sorting="contextApp.state.queryParameters.sort" columns="contextApp.state.queryParameters.columns" minimum-visible-rows="contextApp.state.rows.all.length" - status="contextApp.state.loadingStatus.anchor.status" - reason="contextApp.state.loadingStatus.anchor.reason" + anchor-id="contextApp.anchorId" + anchor-status="contextApp.state.loadingStatus.anchor.status" + anchor-reason="contextApp.state.loadingStatus.anchor.reason" default-step-size="contextApp.state.queryParameters.defaultStepSize" predecessor-count="contextApp.state.queryParameters.predecessorCount" predecessor-available="contextApp.state.rows.predecessors.length" @@ -18,5 +21,4 @@ successor-status="contextApp.state.loadingStatus.successors.status" on-change-successor-count="contextApp.actions.fetchGivenSuccessorRows" use-new-fields-api="contextApp.state.useNewFieldsApi" - top-nav-menu="contextApp.topNavMenu" -> + top-nav-menu="contextApp.topNavMenu"> \ No newline at end of file diff --git a/src/plugins/discover/public/application/angular/context_app.js b/src/plugins/discover/public/application/angular/context_app.js index a90904fa2ccea8..7c9c5f8ce4b425 100644 --- a/src/plugins/discover/public/application/angular/context_app.js +++ b/src/plugins/discover/public/application/angular/context_app.js @@ -34,6 +34,8 @@ getAngularModule().directive('contextApp', function ContextApp() { anchorId: '=', columns: '=', indexPattern: '=', + appState: '=', + stateContainer: '=', filters: '=', predecessorCount: '=', successorCount: '=', @@ -55,7 +57,6 @@ function ContextAppController($scope, Private) { ); this.state.useNewFieldsApi = useNewFieldsApi; this.topNavMenu = navigation.ui.TopNavMenu; - this.actions = _.mapValues( { ...queryParameterActions, diff --git a/src/plugins/discover/public/application/angular/context_app_state.ts b/src/plugins/discover/public/application/angular/context_app_state.ts index 1593b2457019ce..0d9d6d6ea59780 100644 --- a/src/plugins/discover/public/application/angular/context_app_state.ts +++ b/src/plugins/discover/public/application/angular/context_app_state.ts @@ -7,7 +7,7 @@ */ import { Filter } from '../../../../data/public'; -import { AnchorHitRecord } from './context/api/anchor'; +import { EsHitRecord } from './context/api/context'; import { EsHitRecordList } from './context/api/context'; import { SortDirection } from './context/api/utils/sorting'; @@ -48,13 +48,13 @@ export interface QueryParameters { indexPatternId: string; predecessorCount: number; successorCount: number; - sort: [string, SortDirection]; + sort: Array<[string, SortDirection]>; tieBreakerField: string; } interface ContextRows { all: EsHitRecordList; - anchor: AnchorHitRecord; + anchor: EsHitRecord; predecessors: EsHitRecordList; successors: EsHitRecordList; } diff --git a/src/plugins/discover/public/application/angular/context_state.test.ts b/src/plugins/discover/public/application/angular/context_state.test.ts index ed4a74c70112bd..e9294567032c49 100644 --- a/src/plugins/discover/public/application/angular/context_state.test.ts +++ b/src/plugins/discover/public/application/angular/context_state.test.ts @@ -45,8 +45,10 @@ describe('Test Discover Context State', () => { "filters": Array [], "predecessorCount": 4, "sort": Array [ - "time", - "desc", + Array [ + "time", + "desc", + ], ], "successorCount": 4, } @@ -60,7 +62,7 @@ describe('Test Discover Context State', () => { state.setAppState({ predecessorCount: 10 }); state.flushToUrl(); expect(getCurrentUrl()).toMatchInlineSnapshot( - `"/#?_a=(columns:!(_source),filters:!(),predecessorCount:10,sort:!(time,desc),successorCount:4)"` + `"/#?_a=(columns:!(_source),filters:!(),predecessorCount:10,sort:!(!(time,desc)),successorCount:4)"` ); }); test('getState -> url to appState syncing', async () => { @@ -183,7 +185,7 @@ describe('Test Discover Context State', () => { `); state.flushToUrl(); expect(getCurrentUrl()).toMatchInlineSnapshot( - `"/#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,index:'logstash-*',key:extension,negate:!f,params:(query:jpg),type:phrase),query:(match:(extension:(query:jpg,type:phrase))))))&_a=(columns:!(_source),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'logstash-*',key:extension,negate:!t,params:(query:png),type:phrase),query:(match:(extension:(query:png,type:phrase))))),predecessorCount:4,sort:!(time,desc),successorCount:4)"` + `"/#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,index:'logstash-*',key:extension,negate:!f,params:(query:jpg),type:phrase),query:(match:(extension:(query:jpg,type:phrase))))))&_a=(columns:!(_source),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'logstash-*',key:extension,negate:!t,params:(query:png),type:phrase),query:(match:(extension:(query:png,type:phrase))))),predecessorCount:4,sort:!(!(time,desc)),successorCount:4)"` ); }); }); diff --git a/src/plugins/discover/public/application/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts index d60f2e655c4eb5..9cfea7f01e4ab8 100644 --- a/src/plugins/discover/public/application/angular/context_state.ts +++ b/src/plugins/discover/public/application/angular/context_state.ts @@ -35,7 +35,7 @@ export interface AppState { /** * Sorting of the records to be fetched, assumed to be a legacy parameter */ - sort: string[]; + sort: string[][]; /** * Number of records to be fetched after the anchor records (older records) */ @@ -50,7 +50,7 @@ interface GlobalState { filters: Filter[]; } -interface GetStateParams { +export interface GetStateParams { /** * Number of records to be fetched when 'Load' link/button is clicked */ @@ -81,7 +81,7 @@ interface GetStateParams { uiSettings: IUiSettingsClient; } -interface GetStateReturn { +export interface GetStateReturn { /** * Global state, the _g part of the URL */ @@ -276,7 +276,7 @@ function createInitialAppState( columns: ['_source'], filters: [], predecessorCount: parseInt(defaultSize, 10), - sort: [timeFieldName, 'desc'], + sort: [[timeFieldName, 'desc']], successorCount: parseInt(defaultSize, 10), }; if (typeof urlState !== 'object') { diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index fadaffde5c5c31..fa3656d1529d28 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -10,13 +10,13 @@ opts="opts" reset-query="resetQuery" result-state="resultState" + fetch-status="fetchStatus" rows="rows" search-source="volatileSearchSource" state="state" top-nav-menu="topNavMenu" use-new-fields-api="useNewFieldsApi" unmapped-fields-config="unmappedFieldsConfig" - refresh-app-state="refreshAppState" - > + refresh-app-state="refreshAppState"> - + \ No newline at end of file diff --git a/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts b/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts index 8028aa6c086343..0907844aa1c54d 100644 --- a/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts +++ b/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts @@ -8,7 +8,14 @@ import { Capabilities, IUiSettingsClient } from 'kibana/public'; import { popularizeField } from '../../../helpers/popularize_field'; import { IndexPattern, IndexPatternsContract } from '../../../../kibana_services'; -import { AppState } from '../../discover_state'; +import { + AppState as DiscoverState, + GetStateReturn as DiscoverGetStateReturn, +} from '../../discover_state'; +import { + AppState as ContextState, + GetStateReturn as ContextGetStateReturn, +} from '../../context_state'; import { SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; /** @@ -67,8 +74,8 @@ export function getStateColumnActions({ indexPattern: IndexPattern; indexPatterns: IndexPatternsContract; useNewFieldsApi: boolean; - setAppState: (state: Partial) => void; - state: AppState; + setAppState: DiscoverGetStateReturn['setAppState'] | ContextGetStateReturn['setAppState']; + state: DiscoverState | ContextState; }) { function onAddColumn(columnName: string) { if (capabilities.discover.save) { 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 index 4f297643a28f70..ecd7aa8f3dcf46 100644 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.html +++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.html @@ -95,8 +95,8 @@ index-pattern="indexPattern" filter="filter" class="kbnDocTable__row" - ng-class="{'kbnDocTable__row--highlight': row['$$_isAnchor']}" - data-test-subj="docTableRow{{ row['$$_isAnchor'] ? ' docTableAnchorRow' : ''}}" + ng-class="{'kbnDocTable__row--highlight': row['isAnchor']}" + data-test-subj="docTableRow{{ row['isAnchor'] ? ' docTableAnchorRow' : ''}}" on-add-column="onAddColumn" on-remove-column="onRemoveColumn" use-new-fields-api="useNewFieldsApi" diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss b/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss new file mode 100644 index 00000000000000..9ff36ca4527429 --- /dev/null +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss @@ -0,0 +1,24 @@ +@import '../../../../../../core/public/mixins'; + +.dscDocsPage { + @include kibanaFullBodyHeight(54px); // action bar height +} + +.dscDocsContent { + display: flex; + flex-direction: column; + height: 100%; +} + +.dscDocsGrid { + flex: 1 1 100%; + overflow: auto; + + &__cell--highlight { + background-color: tintOrShade($euiColorPrimary, 90%, 70%); + } + + .euiDataGridRowCell.euiDataGridRowCell--firstColumn { + padding: 0; + } +} diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx index 63845ab97b9540..7d947d8412be5c 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx @@ -7,14 +7,34 @@ */ import React from 'react'; -import { ContextAppLegacy } from './context_app_legacy'; -import { IIndexPattern } from '../../../../../data/common/index_patterns'; import { mountWithIntl } from '@kbn/test/jest'; +import { uiSettingsMock as mockUiSettings } from '../../../__mocks__/ui_settings'; +import { IndexPattern } from '../../../../../data/common/index_patterns'; +import { ContextAppLegacy } from './context_app_legacy'; 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 { ContextErrorMessage } from '../context_error_message'; import { TopNavMenuMock } from './__mocks__/top_nav_menu'; +import { AppState, GetStateReturn } from '../../angular/context_state'; +import { SortDirection } from 'src/plugins/data/common'; +import { EsHitRecordList } from '../../angular/context/api/context'; + +jest.mock('../../../kibana_services', () => { + return { + getServices: () => ({ + metadata: { + branch: 'test', + }, + capabilities: { + discover: { + save: true, + }, + }, + uiSettings: mockUiSettings, + }), + }; +}); describe('ContextAppLegacy test', () => { const hit = { @@ -35,16 +55,19 @@ describe('ContextAppLegacy test', () => { }; const indexPattern = { id: 'test_index_pattern', - } as IIndexPattern; + } as IndexPattern; const defaultProps = { columns: ['_source'], filter: () => {}, - hits: [hit], - sorting: ['order_date', 'desc'], + hits: ([hit] as unknown) as EsHitRecordList, + sorting: [['order_date', 'desc']] as Array<[string, SortDirection]>, minimumVisibleRows: 5, indexPattern, - status: 'loaded', - reason: 'no reason', + appState: ({} as unknown) as AppState, + stateContainer: ({} as unknown) as GetStateReturn, + anchorId: 'test_anchor_id', + anchorStatus: 'loaded', + anchorReason: 'no reason', defaultStepSize: 5, predecessorCount: 10, successorCount: 10, @@ -55,6 +78,8 @@ describe('ContextAppLegacy test', () => { predecessorStatus: 'loaded', successorStatus: 'loaded', topNavMenu: TopNavMenuMock, + useNewFieldsApi: false, + isPaginationEnabled: false, }; const topNavProps = { appName: 'context', @@ -80,7 +105,7 @@ describe('ContextAppLegacy test', () => { it('renders loading indicator', () => { const props = { ...defaultProps }; - props.status = 'loading'; + props.anchorStatus = 'loading'; const component = mountWithIntl(); expect(component.find(DocTableLegacy).length).toBe(0); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); @@ -91,8 +116,8 @@ describe('ContextAppLegacy test', () => { it('renders error message', () => { const props = { ...defaultProps }; - props.status = 'failed'; - props.reason = 'something went wrong'; + props.anchorStatus = 'failed'; + props.anchorReason = 'something went wrong'; const component = mountWithIntl(); expect(component.find(DocTableLegacy).length).toBe(0); expect(component.find(TopNavMenuMock).length).toBe(0); diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx index 55c2208105f136..1251687805af19 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx @@ -6,29 +6,43 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useState, Fragment } from 'react'; +import classNames from 'classnames'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; -import { EuiHorizontalRule, EuiText, EuiPageContent, EuiPage } from '@elastic/eui'; +import './context_app_legacy.scss'; +import { EuiHorizontalRule, EuiText, EuiPageContent, EuiPage, EuiSpacer } from '@elastic/eui'; +import { DOC_HIDE_TIME_COLUMN_SETTING, DOC_TABLE_LEGACY } from '../../../../common'; import { ContextErrorMessage } from '../context_error_message'; import { DocTableLegacy, DocTableLegacyProps, } from '../../angular/doc_table/create_doc_table_react'; -import { IIndexPattern, IndexPatternField } from '../../../../../data/common/index_patterns'; +import { IndexPattern } from '../../../../../data/common/index_patterns'; import { LoadingStatus } from '../../angular/context_app_state'; import { ActionBar, ActionBarProps } from '../../angular/context/components/action_bar/action_bar'; import { TopNavMenuProps } from '../../../../../navigation/public'; +import { DiscoverGrid, DiscoverGridProps } from '../discover_grid/discover_grid'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; +import { getServices, SortDirection } from '../../../kibana_services'; +import { GetStateReturn, AppState } from '../../angular/context_state'; +import { useDataGridColumns } from '../../helpers/use_data_grid_columns'; +import { EsHitRecord, EsHitRecordList } from '../../angular/context/api/context'; export interface ContextAppProps { topNavMenu: React.ComponentType; columns: string[]; - hits: Array>; - indexPattern: IIndexPattern; - filter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; + hits: EsHitRecordList; + indexPattern: IndexPattern; + appState: AppState; + stateContainer: GetStateReturn; + filter: DocViewFilterFn; minimumVisibleRows: number; - sorting: string[]; - status: string; - reason: string; + sorting: Array<[string, SortDirection]>; + anchorId: string; + anchorStatus: string; + anchorReason: string; + predecessorStatus: string; + successorStatus: string; defaultStepSize: number; predecessorCount: number; successorCount: number; @@ -36,11 +50,10 @@ export interface ContextAppProps { successorAvailable: number; onChangePredecessorCount: (count: number) => void; onChangeSuccessorCount: (count: number) => void; - predecessorStatus: string; - successorStatus: string; useNewFieldsApi?: boolean; } +const DataGridMemoized = React.memo(DiscoverGrid); const PREDECESSOR_TYPE = 'predecessors'; const SUCCESSOR_TYPE = 'successors'; @@ -49,9 +62,36 @@ function isLoading(status: string) { } export function ContextAppLegacy(renderProps: ContextAppProps) { - const status = renderProps.status; - const isLoaded = status === LoadingStatus.LOADED; - const isFailed = status === LoadingStatus.FAILED; + const services = getServices(); + const { uiSettings: config, capabilities, indexPatterns } = services; + const { + indexPattern, + anchorId, + anchorStatus, + predecessorStatus, + successorStatus, + appState, + stateContainer, + hits: rows, + sorting, + filter, + minimumVisibleRows, + useNewFieldsApi, + } = renderProps; + const [expandedDoc, setExpandedDoc] = useState(undefined); + const isAnchorLoaded = anchorStatus === LoadingStatus.LOADED; + const isFailed = anchorStatus === LoadingStatus.FAILED; + const isLegacy = config.get(DOC_TABLE_LEGACY); + + const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useDataGridColumns({ + capabilities, + config, + indexPattern, + indexPatterns, + setAppState: stateContainer.setAppState, + state: appState, + useNewFieldsApi: !!useNewFieldsApi, + }); const actionBarProps = (type: string) => { const { @@ -60,8 +100,6 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { predecessorCount, predecessorAvailable, successorAvailable, - predecessorStatus, - successorStatus, onChangePredecessorCount, onChangeSuccessorCount, } = renderProps; @@ -73,27 +111,44 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { onChangeCount: isPredecessorType ? onChangePredecessorCount : onChangeSuccessorCount, isLoading: isPredecessorType ? isLoading(predecessorStatus) : isLoading(successorStatus), type, - isDisabled: !isLoaded, + isDisabled: !isAnchorLoaded, } as ActionBarProps; }; const docTableProps = () => { - const { - hits, - filter, - sorting, + return { + ariaLabelledBy: 'surDocumentsAriaLabel', columns, + rows, indexPattern, - minimumVisibleRows, + expandedDoc, + isLoading: isLoading(anchorStatus), + sampleSize: 0, + sort: sorting, + isSortEnabled: false, + showTimeCol: !config.get(DOC_HIDE_TIME_COLUMN_SETTING, false) && !!indexPattern.timeFieldName, + services, useNewFieldsApi, - } = renderProps; + isPaginationEnabled: false, + controlColumnIds: ['openDetails'], + setExpandedDoc, + onFilter: filter, + onAddColumn, + onRemoveColumn, + onSetColumns, + } as DiscoverGridProps; + }; + + const legacyDocTableProps = () => { // @ts-expect-error doesn't implement full DocTableLegacyProps interface return { columns, indexPattern, minimumVisibleRows, - rows: hits, + rows, onFilter: filter, + onAddColumn, + onRemoveColumn, sort: sorting.map((el) => [el]), useNewFieldsApi, } as DocTableLegacyProps; @@ -114,7 +169,7 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { }; const loadingFeedback = () => { - if (status === LoadingStatus.UNINITIALIZED || status === LoadingStatus.LOADING) { + if (anchorStatus === LoadingStatus.UNINITIALIZED || anchorStatus === LoadingStatus.LOADING) { return ( @@ -127,25 +182,42 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { return ( {isFailed ? ( - + ) : ( -
+ - - + + + + + + + + + - {loadingFeedback()} + {isLegacy && loadingFeedback()} - {isLoaded ? ( -
- + {isLegacy ? ( + isAnchorLoaded && ( +
+ +
+ ) + ) : ( +
+
- ) : null} + )} -
+
)} ); diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts b/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts index fc64abfb510258..767ab8c94d80fe 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts @@ -14,11 +14,14 @@ export function createContextAppLegacy(reactDirective: any) { ['filter', { watchDepth: 'reference' }], ['hits', { watchDepth: 'reference' }], ['indexPattern', { watchDepth: 'reference' }], + ['appState', { watchDepth: 'reference' }], + ['stateContainer', { watchDepth: 'reference' }], ['sorting', { watchDepth: 'reference' }], ['columns', { watchDepth: 'collection' }], ['minimumVisibleRows', { watchDepth: 'reference' }], - ['status', { watchDepth: 'reference' }], - ['reason', { watchDepth: 'reference' }], + ['anchorId', { watchDepth: 'reference' }], + ['anchorStatus', { watchDepth: 'reference' }], + ['anchorReason', { watchDepth: 'reference' }], ['defaultStepSize', { watchDepth: 'reference' }], ['predecessorCount', { watchDepth: 'reference' }], ['predecessorAvailable', { watchDepth: 'reference' }], diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts index f8c74c07457aa1..049c9ac177eeab 100644 --- a/src/plugins/discover/public/application/components/create_discover_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_directive.ts @@ -20,6 +20,7 @@ export function createDiscoverDirective(reactDirective: any) { ['opts', { watchDepth: 'reference' }], ['resetQuery', { watchDepth: 'reference' }], ['resultState', { watchDepth: 'reference' }], + ['fetchStatus', { watchDepth: 'reference' }], ['rows', { watchDepth: 'reference' }], ['savedSearch', { watchDepth: 'reference' }], ['searchSource', { watchDepth: 'reference' }], diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 90dfd2ef9dce9d..f962c56cc4690c 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -34,9 +34,12 @@ import { esFilters, IndexPatternField, search } from '../../../../data/public'; import { DiscoverSidebarResponsive } from './sidebar'; import { DiscoverProps } from './types'; import { SortPairArr } from '../angular/doc_table/lib/get_sort'; -import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + DOC_TABLE_LEGACY, + SEARCH_FIELDS_FROM_SOURCE, +} from '../../../common'; import { popularizeField } from '../helpers/popularize_field'; -import { getStateColumnActions } from '../angular/doc_table/actions/columns'; import { DocViewFilterFn } from '../doc_views/doc_views_types'; import { DiscoverGrid } from './discover_grid/discover_grid'; import { DiscoverTopNav } from './discover_topnav'; @@ -44,6 +47,7 @@ import { ElasticSearchHit } from '../doc_views/doc_views_types'; import { setBreadcrumbsTitle } from '../helpers/breadcrumbs'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; import { InspectorSession } from '../../../../inspector/public'; +import { useDataGridColumns } from '../helpers/use_data_grid_columns'; const DocTableLegacyMemoized = React.memo(DocTableLegacy); const SidebarMemoized = React.memo(DiscoverSidebarResponsive); @@ -96,7 +100,7 @@ export function Discover({ }, [opts.chartAggConfigs]); const contentCentered = resultState === 'uninitialized'; - const isLegacy = services.uiSettings.get('doc_table:legacy'); + const isLegacy = services.uiSettings.get(DOC_TABLE_LEGACY); const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); const updateQuery = useCallback( (_payload, isUpdate?: boolean) => { @@ -108,6 +112,16 @@ export function Discover({ [opts] ); + const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useDataGridColumns({ + capabilities, + config, + indexPattern, + indexPatterns, + setAppState, + state, + useNewFieldsApi, + }); + useEffect(() => { const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; chrome.docTitle.change(`Discover${pageTitleSuffix}`); @@ -116,20 +130,6 @@ export function Discover({ addHelpMenuToAppChrome(chrome, docLinks); }, [savedSearch, chrome, docLinks]); - const { onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useMemo( - () => - getStateColumnActions({ - capabilities, - config, - indexPattern, - indexPatterns, - setAppState, - state, - useNewFieldsApi, - }), - [capabilities, config, indexPattern, indexPatterns, setAppState, state, useNewFieldsApi] - ); - const onOpenInspector = useCallback(() => { // prevent overlapping setExpandedDoc(undefined); @@ -225,12 +225,6 @@ export function Discover({ } }; - const columns = useMemo(() => { - if (!state.columns) { - return []; - } - return useNewFieldsApi ? state.columns.filter((col) => col !== '_source') : state.columns; - }, [state, useNewFieldsApi]); return ( @@ -439,13 +433,13 @@ export function Discover({ searchTitle={opts.savedSearch.lastSavedTitle} setExpandedDoc={setExpandedDoc} showTimeCol={ - !config.get('doc_table:hideTimeColumn', false) && + !config.get(DOC_HIDE_TIME_COLUMN_SETTING, false) && !!indexPattern.timeFieldName } services={services} settings={state.grid} - onAddColumn={onAddColumn} onFilter={onAddFilter as DocViewFilterFn} + onAddColumn={onAddColumn} onRemoveColumn={onRemoveColumn} onSetColumns={onSetColumns} onSort={onSort} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss index cb1b9a8ea191ea..4c5fe2de64946b 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.scss @@ -47,6 +47,8 @@ // We only truncate if the cell is not a control column. .euiDataGridHeader { + display: flex; // fixing row header in firefox + .euiDataGridHeaderCell__content { @include euiTextTruncate; overflow: hidden; @@ -66,6 +68,7 @@ text-align: right; } +.euiDataGrid__loading, .euiDataGrid__noResults { display: flex; flex-direction: column; 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 f969eb32f3791a..65a6ee80564e9f 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 @@ -14,11 +14,12 @@ import { EuiDataGridStyle, EuiDataGridProps, EuiDataGrid, - EuiIcon, EuiScreenReaderOnly, EuiSpacer, EuiText, htmlIdGenerator, + EuiLoadingSpinner, + EuiIcon, } from '@elastic/eui'; import { IndexPattern } from '../../../kibana_services'; import { DocViewFilterFn, ElasticSearchHit } from '../../doc_views/doc_views_types'; @@ -88,9 +89,9 @@ export interface DiscoverGridProps { */ onSetColumns: (columns: string[]) => void; /** - * function to change sorting of the documents + * function to change sorting of the documents, skipped when isSortEnabled is set to false */ - onSort: (sort: string[][]) => void; + onSort?: (sort: string[][]) => void; /** * Array of documents provided by Elasticsearch */ @@ -123,6 +124,10 @@ export interface DiscoverGridProps { * Determines whether the time columns should be displayed (legacy settings) */ showTimeCol: boolean; + /** + * Manage user sorting control + */ + isSortEnabled?: boolean; /** * Current sort setting */ @@ -131,6 +136,14 @@ export interface DiscoverGridProps { * How the data is fetched */ useNewFieldsApi: boolean; + /** + * Manage pagination control + */ + isPaginationEnabled?: boolean; + /** + * List of used control columns (available: 'openDetails', 'select') + */ + controlColumnIds?: string[]; } export const EuiDataGridMemoized = React.memo((props: EuiDataGridProps) => { @@ -159,6 +172,9 @@ export const DiscoverGrid = ({ showTimeCol, sort, useNewFieldsApi, + isSortEnabled = true, + isPaginationEnabled = true, + controlColumnIds = ['openDetails', 'select'], }: DiscoverGridProps) => { const [selectedDocs, setSelectedDocs] = useState([]); const [isFilterActive, setIsFilterActive] = useState(false); @@ -210,14 +226,16 @@ export const DiscoverGrid = ({ const onChangePage = (pageIndex: number) => setPagination((paginationData) => ({ ...paginationData, pageIndex })); - return { - onChangeItemsPerPage, - onChangePage, - pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex, - pageSize: pagination.pageSize, - pageSizeOptions: pageSizeArr, - }; - }, [pagination, pageCount]); + return isPaginationEnabled + ? { + onChangeItemsPerPage, + onChangePage, + pageIndex: pagination.pageIndex > pageCount - 1 ? 0 : pagination.pageIndex, + pageSize: pagination.pageSize, + pageSizeOptions: pageSizeArr, + } + : undefined; + }, [pagination, pageCount, isPaginationEnabled]); /** * Sorting @@ -226,9 +244,11 @@ export const DiscoverGrid = ({ const onTableSort = useCallback( (sortingColumnsData) => { - onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction])); + if (isSortEnabled && onSort) { + onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction])); + } }, - [onSort] + [onSort, isSortEnabled] ); /** @@ -253,8 +273,16 @@ export const DiscoverGrid = ({ const randomId = useMemo(() => htmlIdGenerator()(), []); const euiGridColumns = useMemo( - () => getEuiGridColumns(displayedColumns, settings, indexPattern, showTimeCol, defaultColumns), - [displayedColumns, indexPattern, showTimeCol, settings, defaultColumns] + () => + getEuiGridColumns( + displayedColumns, + settings, + indexPattern, + showTimeCol, + defaultColumns, + isSortEnabled + ), + [displayedColumns, indexPattern, showTimeCol, settings, defaultColumns, isSortEnabled] ); const schemaDetectors = useMemo(() => getSchemaDetectors(), []); const columnsVisibility = useMemo( @@ -266,11 +294,16 @@ export const DiscoverGrid = ({ }), [displayedColumns, indexPattern, showTimeCol, onSetColumns] ); - const sorting = useMemo(() => ({ columns: sortingColumns, onSort: onTableSort }), [ - sortingColumns, - onTableSort, - ]); - const lead = useMemo(() => getLeadControlColumns(), []); + const sorting = useMemo(() => { + if (isSortEnabled) { + return { columns: sortingColumns, onSort: onTableSort }; + } + return { columns: sortingColumns, onSort: () => {} }; + }, [sortingColumns, onTableSort, isSortEnabled]); + const lead = useMemo( + () => getLeadControlColumns().filter(({ id }) => controlColumnIds.includes(id)), + [controlColumnIds] + ); const additionalControls = useMemo( () => @@ -286,6 +319,18 @@ export const DiscoverGrid = ({ [usedSelectedDocs, isFilterActive, rows, setIsFilterActive] ); + if (!rowCount && isLoading) { + return ( +
+ + + + + +
+ ); + } + if (!rowCount) { return (
@@ -348,10 +393,12 @@ export const DiscoverGrid = ({ ? { ...toolbarVisibility, showColumnSelector: false, + showSortSelector: isSortEnabled, additionalControls, } : { ...toolbarVisibility, + showSortSelector: isSortEnabled, additionalControls, } } diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx index 93b5bf8fde0c18..3cbac90aa39cb3 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.test.tsx @@ -12,7 +12,14 @@ import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_ describe('Discover grid columns ', function () { it('returns eui grid columns without time column', async () => { - const actual = getEuiGridColumns(['extension', 'message'], {}, indexPatternMock, false, false); + const actual = getEuiGridColumns( + ['extension', 'message'], + {}, + indexPatternMock, + false, + false, + true + ); expect(actual).toMatchInlineSnapshot(` Array [ Object { @@ -54,6 +61,7 @@ describe('Discover grid columns ', function () { {}, indexPatternWithTimefieldMock, false, + true, true ); expect(actual).toMatchInlineSnapshot(` @@ -94,7 +102,8 @@ describe('Discover grid columns ', function () { {}, indexPatternWithTimefieldMock, true, - false + false, + true ); expect(actual).toMatchInlineSnapshot(` Array [ diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx index df7e2285a07544..3a27772662b56f 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx @@ -53,7 +53,8 @@ export function buildEuiGridColumn( columnName: string, columnWidth: number | undefined = 0, indexPattern: IndexPattern, - defaultColumns: boolean + defaultColumns: boolean, + isSortEnabled: boolean ) { const timeString = i18n.translate('discover.timeLabel', { defaultMessage: 'Time', @@ -62,7 +63,7 @@ export function buildEuiGridColumn( const column: EuiDataGridColumn = { id: columnName, schema: getSchemaByKbnType(indexPatternField?.type), - isSortable: indexPatternField?.sortable === true, + isSortable: isSortEnabled && indexPatternField?.sortable === true, display: columnName === '_source' ? i18n.translate('discover.grid.documentHeader', { @@ -100,7 +101,8 @@ export function getEuiGridColumns( settings: DiscoverGridSettings | undefined, indexPattern: IndexPattern, showTimeCol: boolean, - defaultColumns: boolean + defaultColumns: boolean, + isSortEnabled: boolean ) { const timeFieldName = indexPattern.timeFieldName; const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0; @@ -108,12 +110,12 @@ export function getEuiGridColumns( if (showTimeCol && indexPattern.timeFieldName && !columns.find((col) => col === timeFieldName)) { const usedColumns = [indexPattern.timeFieldName, ...columns]; return usedColumns.map((column) => - buildEuiGridColumn(column, getColWidth(column), indexPattern, defaultColumns) + buildEuiGridColumn(column, getColWidth(column), indexPattern, defaultColumns, isSortEnabled) ); } return columns.map((column) => - buildEuiGridColumn(column, getColWidth(column), indexPattern, defaultColumns) + buildEuiGridColumn(column, getColWidth(column), indexPattern, defaultColumns, isSortEnabled) ); } diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx index 9ebe3ee95f7974..41cf3f5a68edbf 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx @@ -51,7 +51,14 @@ describe('document selection', () => { const component = mountWithIntl( - + ); @@ -73,7 +80,14 @@ describe('document selection', () => { const component = mountWithIntl( - + ); @@ -95,7 +109,14 @@ describe('document selection', () => { const component = mountWithIntl( - + ); @@ -117,7 +138,14 @@ describe('document selection', () => { const component = mountWithIntl( - + ); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx index a99819fa9e0576..03c17c801fa963 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx @@ -5,7 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useCallback, useState, useContext, useMemo } from 'react'; +import React, { useCallback, useState, useContext, useMemo, useEffect } from 'react'; +import classNames from 'classnames'; import { EuiButtonEmpty, EuiContextMenuItem, @@ -13,9 +14,11 @@ import { EuiCopy, EuiPopover, EuiCheckbox, + EuiDataGridCellValueElementProps, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import classNames from 'classnames'; +import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; +import themeLight from '@elastic/eui/dist/eui_theme_light.json'; import { ElasticSearchHit } from '../../doc_views/doc_views_types'; import { DiscoverGridContext } from './discover_grid_context'; @@ -27,11 +30,25 @@ export const getDocId = (doc: ElasticSearchHit & { _routing?: string }) => { const routing = doc._routing ? doc._routing : ''; return [doc._index, doc._id, routing].join('::'); }; -export const SelectButton = ({ rowIndex }: { rowIndex: number }) => { - const ctx = useContext(DiscoverGridContext); - const doc = useMemo(() => ctx.rows[rowIndex], [ctx.rows, rowIndex]); +export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { + const { selectedDocs, expanded, rows, isDarkMode, setSelectedDocs } = useContext( + DiscoverGridContext + ); + const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]); const id = useMemo(() => getDocId(doc), [doc]); - const checked = useMemo(() => ctx.selectedDocs.includes(id), [ctx.selectedDocs, id]); + const checked = useMemo(() => selectedDocs.includes(id), [selectedDocs, id]); + + useEffect(() => { + if (expanded && doc && expanded._id === doc._id) { + setCellProps({ + style: { + backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, + }, + }); + } else { + setCellProps({ style: undefined }); + } + }, [expanded, doc, setCellProps, isDarkMode]); return ( { data-test-subj={`dscGridSelectDoc-${id}`} onChange={() => { if (checked) { - const newSelection = ctx.selectedDocs.filter((docId) => docId !== id); - ctx.setSelectedDocs(newSelection); + const newSelection = selectedDocs.filter((docId) => docId !== id); + setSelectedDocs(newSelection); } else { - ctx.setSelectedDocs([...ctx.selectedDocs, id]); + setSelectedDocs([...selectedDocs, id]); } }} /> diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx index 115acb84b95d80..b11733c1595206 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.tsx @@ -12,6 +12,7 @@ import themeDark from '@elastic/eui/dist/eui_theme_dark.json'; import themeLight from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { DiscoverGridContext } from './discover_grid_context'; +import { EsHitRecord } from '../../angular/context/api/context'; /** * Button to expand a given row */ @@ -19,7 +20,11 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle const { expanded, setExpanded, rows, isDarkMode } = useContext(DiscoverGridContext); const current = rows[rowIndex]; useEffect(() => { - if (expanded && current && expanded._id === current._id) { + if ((current as EsHitRecord).isAnchor) { + setCellProps({ + className: 'dscDocsGrid__cell--highlight', + }); + } else if (expanded && current && expanded._id === current._id) { setCellProps({ style: { backgroundColor: isDarkMode ? themeDark.euiColorHighlight : themeLight.euiColorHighlight, diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx index fc3dd499f92e08..b3c205e072508f 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx @@ -21,6 +21,7 @@ import { ElasticSearchHit } from '../../doc_views/doc_views_types'; import { DiscoverGridContext } from './discover_grid_context'; import { JsonCodeEditor } from '../json_code_editor/json_code_editor'; import { defaultMonacoEditorWidth } from './constants'; +import { EsHitRecord } from '../../angular/context/api/context'; export const getRenderCellValueFn = ( indexPattern: IndexPattern, @@ -38,7 +39,11 @@ export const getRenderCellValueFn = ( const ctx = useContext(DiscoverGridContext); useEffect(() => { - if (ctx.expanded && row && ctx.expanded._id === row._id) { + if ((row as EsHitRecord).isAnchor) { + setCellProps({ + className: 'dscDocsGrid__cell--highlight', + }); + } else if (ctx.expanded && row && ctx.expanded._id === row._id) { setCellProps({ style: { backgroundColor: ctx.isDarkMode diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index 99ecb4c11eef2c..1e3c7e77d36150 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -33,6 +33,7 @@ import { getServices, IndexPattern, ISearchSource } from '../../kibana_services' import { SEARCH_EMBEDDABLE_TYPE } from './constants'; import { SavedSearch } from '../..'; import { + DOC_HIDE_TIME_COLUMN_SETTING, SAMPLE_SIZE_SETTING, SEARCH_FIELDS_FROM_SOURCE, SORT_DEFAULT_ORDER_SETTING, @@ -256,7 +257,7 @@ export class SearchEmbeddable if (this.savedSearch.grid) { searchScope.settings = this.savedSearch.grid; } - searchScope.showTimeCol = !this.services.uiSettings.get('doc_table:hideTimeColumn', false); + searchScope.showTimeCol = !this.services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false); searchScope.filter = async (field, value, operator) => { let filters = esFilters.generateFilters( diff --git a/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx b/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx new file mode 100644 index 00000000000000..c9e1899aff8de6 --- /dev/null +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx @@ -0,0 +1,67 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useDataGridColumns } from './use_data_grid_columns'; +import { indexPatternMock } from '../../__mocks__/index_pattern'; +import { configMock } from '../../__mocks__/config'; +import { indexPatternsMock } from '../../__mocks__/index_patterns'; +import { AppState } from '../angular/context_state'; +import { Capabilities } from '../../../../../core/types'; + +describe('useDataGridColumns', () => { + const defaultProps = { + capabilities: ({ discover: { save: true } } as unknown) as Capabilities, + config: configMock, + indexPattern: indexPatternMock, + indexPatterns: indexPatternsMock, + setAppState: () => {}, + state: { + columns: ['Time', 'message'], + } as AppState, + useNewFieldsApi: false, + }; + + test('should return valid result', () => { + const { result } = renderHook(() => { + return useDataGridColumns(defaultProps); + }); + + expect(result.current.columns).toEqual(['Time', 'message']); + expect(result.current.onAddColumn).toBeInstanceOf(Function); + expect(result.current.onRemoveColumn).toBeInstanceOf(Function); + expect(result.current.onMoveColumn).toBeInstanceOf(Function); + expect(result.current.onSetColumns).toBeInstanceOf(Function); + }); + + test('should skip _source column when useNewFieldsApi is set to true', () => { + const { result } = renderHook(() => { + return useDataGridColumns({ + ...defaultProps, + state: { + columns: ['Time', '_source'], + }, + useNewFieldsApi: true, + }); + }); + + expect(result.current.columns).toEqual(['Time']); + }); + + test('should return empty columns array', () => { + const { result } = renderHook(() => { + return useDataGridColumns({ + ...defaultProps, + state: { + columns: [], + }, + }); + }); + expect(result.current.columns).toEqual([]); + }); +}); 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 new file mode 100644 index 00000000000000..c913b9abd1b433 --- /dev/null +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts @@ -0,0 +1,70 @@ +/* + * 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 { useMemo } from 'react'; + +import { Capabilities, IUiSettingsClient } from 'kibana/public'; +import { IndexPattern, IndexPatternsContract } from '../../kibana_services'; +import { + AppState as DiscoverState, + GetStateReturn as DiscoverGetStateReturn, +} from '../angular/discover_state'; +import { + AppState as ContextState, + GetStateReturn as ContextGetStateReturn, +} from '../angular/context_state'; +import { getStateColumnActions } from '../angular/doc_table/actions/columns'; + +interface UseDataGridColumnsProps { + capabilities: Capabilities; + config: IUiSettingsClient; + indexPattern: IndexPattern; + indexPatterns: IndexPatternsContract; + useNewFieldsApi: boolean; + setAppState: DiscoverGetStateReturn['setAppState'] | ContextGetStateReturn['setAppState']; + state: DiscoverState | ContextState; +} + +export const useDataGridColumns = ({ + capabilities, + config, + indexPattern, + indexPatterns, + setAppState, + state, + useNewFieldsApi, +}: UseDataGridColumnsProps) => { + const { onAddColumn, onRemoveColumn, onSetColumns, onMoveColumn } = useMemo( + () => + getStateColumnActions({ + capabilities, + config, + indexPattern, + indexPatterns, + setAppState, + state, + useNewFieldsApi, + }), + [capabilities, config, indexPattern, indexPatterns, setAppState, state, useNewFieldsApi] + ); + + const columns = useMemo(() => { + if (!state.columns) { + return []; + } + return useNewFieldsApi ? state.columns.filter((col) => col !== '_source') : state.columns; + }, [state, useNewFieldsApi]); + + return { + columns, + onAddColumn, + onRemoveColumn, + onMoveColumn, + onSetColumns, + }; +}; diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts index 275ac011820bed..ee60660ae4a9eb 100644 --- a/test/functional/apps/discover/_data_grid_context.ts +++ b/test/functional/apps/discover/_data_grid_context.ts @@ -19,7 +19,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const filterBar = getService('filterBar'); const dataGrid = getService('dataGrid'); - const docTable = getService('docTable'); const PageObjects = getPageObjects([ 'common', 'discover', @@ -67,16 +66,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle({ rowIndex: 0 }); const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); await rowActions[1].click(); - // entering the context view (contains the legacy type) - const contextFields = await docTable.getFields(); + + const contextFields = await dataGrid.getFields(); const anchorTimestamp = contextFields[0][0]; + return anchorTimestamp === firstTimestamp; }); }); it('should open the context view with the same columns', async () => { - const columnNames = await docTable.getHeaderFields(); - expect(columnNames).to.eql(['Time', ...TEST_COLUMN_NAMES]); + const columnNames = await dataGrid.getHeaderFields(); + expect(columnNames).to.eql(['Time (@timestamp)', ...TEST_COLUMN_NAMES]); }); it('should open the context view with the filters disabled', async () => { @@ -111,7 +111,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await browser.getCurrentUrl()).to.contain('#/context'); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.waitFor('document table has a length of 6', async () => { - const nrOfDocs = (await docTable.getBodyRows()).length; + const nrOfDocs = (await dataGrid.getBodyRows()).length; return nrOfDocs === 6; }); }); diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 8a5e4586fc1688..f2079c02ef5b5c 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -135,6 +135,7 @@ export class DataGridService extends FtrService { if (!table) { return []; } + const cells = await table.findAllByCssSelector('.euiDataGridRowCell'); const rows: WebElementWrapper[][] = []; @@ -173,14 +174,13 @@ export class DataGridService extends FtrService { } public async getHeaderFields(): Promise { - const result = await this.find.allByCssSelector('.euiDataGridHeaderCell__content'); + const result = await this.find.allByCssSelector( + '.euiDataGridHeaderCell__button > .euiDataGridHeaderCell__content' + ); + const textArr = []; - let idx = 0; for (const cell of result) { - if (idx > 1) { - textArr.push(await cell.getVisibleText()); - } - idx++; + textArr.push(await cell.getVisibleText()); } return Promise.resolve(textArr); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7442b41493c79b..7eb573537abf24 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1556,7 +1556,6 @@ "discover.bucketIntervalTooltip.tooLargeBucketsText": "大きすぎるバケット", "discover.bucketIntervalTooltip.tooManyBucketsText": "バケットが多すぎます", "discover.clearSelection": "選択した項目をクリア", - "discover.context.breadcrumb": "{indexPatternTitle}#{docId} のコンテキスト", "discover.context.failedToLoadAnchorDocumentDescription": "アンカードキュメントの読み込みに失敗しました", "discover.context.failedToLoadAnchorDocumentErrorDescription": "アンカードキュメントの読み込みに失敗しました。", "discover.context.loadButtonLabel": "読み込み", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7a8831dc15f840..11df1d5932242b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1565,7 +1565,6 @@ "discover.bucketIntervalTooltip.tooLargeBucketsText": "存储桶过大", "discover.bucketIntervalTooltip.tooManyBucketsText": "存储桶过多", "discover.clearSelection": "清除所选内容", - "discover.context.breadcrumb": "{indexPatternTitle}#{docId} 的上下文", "discover.context.failedToLoadAnchorDocumentDescription": "无法加载定位点文档", "discover.context.failedToLoadAnchorDocumentErrorDescription": "无法加载定位点文档。", "discover.context.loadButtonLabel": "加载",