diff --git a/src/plugins/discover/public/application/context/context_app.test.tsx b/src/plugins/discover/public/application/context/context_app.test.tsx index a31557124d49a..b97c9ed9fe8d9 100644 --- a/src/plugins/discover/public/application/context/context_app.test.tsx +++ b/src/plugins/discover/public/application/context/context_app.test.tsx @@ -14,17 +14,49 @@ import { mockTopNavMenu } from './__mocks__/top_nav_menu'; import { ContextAppContent } from './context_app_content'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { ContextApp } from './context_app'; -import { setServices } from '../../kibana_services'; import { DiscoverServices } from '../../build_services'; import { indexPatternsMock } from '../../__mocks__/index_patterns'; import { act } from 'react-dom/test-utils'; import { uiSettingsMock } from '../../__mocks__/ui_settings'; import { themeServiceMock } from '../../../../../core/public/mocks'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; const mockFilterManager = createFilterManagerMock(); const mockNavigationPlugin = { ui: { TopNavMenu: mockTopNavMenu } }; describe('ContextApp test', () => { + const services = { + data: { + search: { + searchSource: { + createEmpty: jest.fn(), + }, + }, + }, + capabilities: { + discover: { + save: true, + }, + indexPatterns: { + save: true, + }, + }, + indexPatterns: indexPatternsMock, + toastNotifications: { addDanger: () => {} }, + navigation: mockNavigationPlugin, + core: { + notifications: { toasts: [] }, + theme: { theme$: themeServiceMock.createStartContract().theme$ }, + }, + history: () => {}, + fieldFormats: { + getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), + getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), + }, + filterManager: mockFilterManager, + uiSettings: uiSettingsMock, + } as unknown as DiscoverServices; + const defaultProps = { indexPattern: indexPatternMock, anchorId: 'mocked_anchor_id', @@ -41,42 +73,16 @@ describe('ContextApp test', () => { useDefaultBehaviors: true, }; - beforeEach(() => { - setServices({ - data: { - search: { - searchSource: { - createEmpty: jest.fn(), - }, - }, - }, - capabilities: { - discover: { - save: true, - }, - indexPatterns: { - save: true, - }, - }, - indexPatterns: indexPatternsMock, - toastNotifications: { addDanger: () => {} }, - navigation: mockNavigationPlugin, - core: { - notifications: { toasts: [] }, - theme: { theme$: themeServiceMock.createStartContract().theme$ }, - }, - history: () => {}, - fieldFormats: { - getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), - getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), - }, - filterManager: mockFilterManager, - uiSettings: uiSettingsMock, - } as unknown as DiscoverServices); - }); + const mountComponent = () => { + return mountWithIntl( + + + + ); + }; it('renders correctly', async () => { - const component = mountWithIntl(); + const component = mountComponent(); await waitFor(() => { expect(component.find(ContextAppContent).length).toBe(1); const topNavMenu = component.find(mockTopNavMenu); @@ -86,7 +92,7 @@ describe('ContextApp test', () => { }); it('should set filters correctly', async () => { - const component = mountWithIntl(); + const component = mountComponent(); await act(async () => { component.find(ContextAppContent).invoke('addFilter')( diff --git a/src/plugins/discover/public/application/context/context_app.tsx b/src/plugins/discover/public/application/context/context_app.tsx index 8faabdbd0682d..f93bc2b49fdd5 100644 --- a/src/plugins/discover/public/application/context/context_app.tsx +++ b/src/plugins/discover/public/application/context/context_app.tsx @@ -17,7 +17,6 @@ import { DOC_TABLE_LEGACY, SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; import { ContextErrorMessage } from './components/context_error_message'; import { DataView, DataViewField } from '../../../../data/common'; import { LoadingStatus } from './services/context_query_state'; -import { getServices } from '../../kibana_services'; import { AppState, isEqualFilters } from './services/context_state'; import { useColumns } from '../../utils/use_data_grid_columns'; import { useContextAppState } from './utils/use_context_app_state'; @@ -26,6 +25,7 @@ import { popularizeField } from '../../utils/popularize_field'; import { ContextAppContent } from './context_app_content'; import { SurrDocType } from './services/context'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; const ContextAppContentMemoized = memo(ContextAppContent); @@ -35,7 +35,7 @@ export interface ContextAppProps { } export const ContextApp = ({ indexPattern, anchorId }: ContextAppProps) => { - const services = getServices(); + const services = useDiscoverServices(); const { uiSettings, capabilities, indexPatterns, navigation, filterManager } = services; const isLegacy = useMemo(() => uiSettings.get(DOC_TABLE_LEGACY), [uiSettings]); @@ -56,7 +56,6 @@ export const ContextApp = ({ indexPattern, anchorId }: ContextAppProps) => { indexPattern, appState, useNewFieldsApi, - services, }); /** * Reset state when anchor changes @@ -163,7 +162,6 @@ export const ContextApp = ({ indexPattern, anchorId }: ContextAppProps) => { { - let hit; - let defaultProps: ContextAppContentProps; - - beforeEach(() => { - setServices(discoverServiceMock); - - hit = { + const mountComponent = ({ + anchorStatus, + isLegacy, + }: { + anchorStatus?: LoadingStatus; + isLegacy?: boolean; + }) => { + const hit = { _id: '123', _index: 'test_index', _score: null, @@ -48,12 +49,12 @@ describe('ContextAppContent test', () => { }, sort: [1603114502000, 2092], }; - defaultProps = { + const props = { columns: ['order_date', '_source'], indexPattern: indexPatternMock, appState: {} as unknown as AppState, stateContainer: {} as unknown as GetStateReturn, - anchorStatus: LoadingStatus.LOADED, + anchorStatus: anchorStatus || LoadingStatus.LOADED, predecessorsStatus: LoadingStatus.LOADED, successorsStatus: LoadingStatus.LOADED, rows: [hit] as unknown as EsHitRecordList, @@ -67,16 +68,21 @@ describe('ContextAppContent test', () => { onAddColumn: () => {}, onRemoveColumn: () => {}, onSetColumns: () => {}, - services: getServices(), sort: [['order_date', 'desc']] as Array<[string, SortDirection]>, - isLegacy: true, + isLegacy: isLegacy ?? true, setAppState: () => {}, addFilter: () => {}, } as unknown as ContextAppContentProps; - }); + + return mountWithIntl( + + + + ); + }; it('should render legacy table correctly', () => { - const component = mountWithIntl(); + const component = mountComponent({}); expect(component.find(DocTableWrapper).length).toBe(1); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); expect(loadingIndicator.length).toBe(0); @@ -84,17 +90,14 @@ describe('ContextAppContent test', () => { }); it('renders loading indicator', () => { - const props = { ...defaultProps }; - props.anchorStatus = LoadingStatus.LOADING; - const component = mountWithIntl(); + const component = mountComponent({ anchorStatus: LoadingStatus.LOADING }); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); expect(component.find(DocTableWrapper).length).toBe(1); expect(loadingIndicator.length).toBe(1); }); it('should render discover grid correctly', () => { - const props = { ...defaultProps, isLegacy: false }; - const component = mountWithIntl(); + const component = mountComponent({ isLegacy: false }); expect(component.find(DiscoverGrid).length).toBe(1); }); }); diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx index 8c23dc2608973..67efd36f1bc7c 100644 --- a/src/plugins/discover/public/application/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.tsx @@ -17,19 +17,18 @@ import { DiscoverGrid } from '../../components/discover_grid/discover_grid'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; import { AppState } from './services/context_state'; import { SurrDocType } from './services/context'; -import { DiscoverServices } from '../../build_services'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from './services/constants'; import { DocTableContext } from '../../components/doc_table/doc_table_context'; import { EsHitRecordList } from '../types'; import { SortPairArr } from '../../components/doc_table/lib/get_sort'; import { ElasticSearchHit } from '../../types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; export interface ContextAppContentProps { columns: string[]; onAddColumn: (columnsName: string) => void; onRemoveColumn: (columnsName: string) => void; onSetColumns: (columnsNames: string[], hideTimeColumn: boolean) => void; - services: DiscoverServices; indexPattern: DataView; predecessorCount: number; successorCount: number; @@ -60,7 +59,6 @@ export function ContextAppContent({ onAddColumn, onRemoveColumn, onSetColumns, - services, indexPattern, predecessorCount, successorCount, @@ -75,7 +73,7 @@ export function ContextAppContent({ setAppState, addFilter, }: ContextAppContentProps) { - const { uiSettings: config } = services; + const { uiSettings: config } = useDiscoverServices(); const [expandedDoc, setExpandedDoc] = useState(); const isAnchorLoading = @@ -154,7 +152,6 @@ export function ContextAppContent({ sort={sort as SortPairArr[]} isSortEnabled={false} showTimeCol={showTimeCol} - services={services} useNewFieldsApi={useNewFieldsApi} isPaginationEnabled={false} controlColumnIds={controlColumnIds} diff --git a/src/plugins/discover/public/application/context/context_app_route.tsx b/src/plugins/discover/public/application/context/context_app_route.tsx index d81b540418f99..5bbbefa5cdb93 100644 --- a/src/plugins/discover/public/application/context/context_app_route.tsx +++ b/src/plugins/discover/public/application/context/context_app_route.tsx @@ -14,16 +14,16 @@ import { ContextApp } from './context_app'; import { getRootBreadcrumbs } from '../../utils/breadcrumbs'; import { LoadingIndicator } from '../../components/common/loading_indicator'; import { useIndexPattern } from '../../utils/use_index_pattern'; -import { DiscoverRouteProps } from '../types'; import { useMainRouteBreadcrumb } from '../../utils/use_navigation_props'; +import { useDiscoverServices } from '../../utils/use_discover_services'; export interface ContextUrlParams { indexPatternId: string; id: string; } -export function ContextAppRoute(props: DiscoverRouteProps) { - const { services } = props; +export function ContextAppRoute() { + const services = useDiscoverServices(); const { chrome } = services; const { indexPatternId, id } = useParams(); diff --git a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts index 810bd76ef60ad..136a2cb0a3acc 100644 --- a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts @@ -11,9 +11,7 @@ import { get, last } from 'lodash'; import { DataView, SortDirection } from 'src/plugins/data/common'; import { createContextSearchSourceStub } from './_stubs'; import { fetchSurroundingDocs, SurrDocType } from './context'; -import { setServices } from '../../../kibana_services'; -import { Query } from '../../../../../data/public'; -import { DiscoverServices } from '../../../build_services'; +import { DataPublicPluginStart, Query } from '../../../../../data/public'; import { EsHitRecord, EsHitRecordList } from '../../types'; const MS_PER_DAY = 24 * 60 * 60 * 1000; @@ -29,6 +27,7 @@ interface Timestamp { } describe('context predecessors', function () { + let dataPluginMock: DataPublicPluginStart; let fetchPredecessors: ( timeValIso: string, timeValNr: number, @@ -36,6 +35,7 @@ describe('context predecessors', function () { tieBreakerValue: number, size: number ) => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockSearchSource: any; const indexPattern = { @@ -48,16 +48,13 @@ describe('context predecessors', function () { describe('function fetchPredecessors', function () { beforeEach(() => { mockSearchSource = createContextSearchSourceStub('@timestamp'); - - setServices({ - data: { - search: { - searchSource: { - createEmpty: jest.fn().mockImplementation(() => mockSearchSource), - }, + dataPluginMock = { + search: { + searchSource: { + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, - } as unknown as DiscoverServices); + } as unknown as DataPublicPluginStart; fetchPredecessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size = 10) => { const anchor = { @@ -74,7 +71,8 @@ describe('context predecessors', function () { tieBreakerField, SortDirection.desc, size, - [] + [], + dataPluginMock ); }; }); @@ -192,15 +190,13 @@ describe('context predecessors', function () { beforeEach(() => { mockSearchSource = createContextSearchSourceStub('@timestamp'); - setServices({ - data: { - search: { - searchSource: { - createEmpty: jest.fn().mockImplementation(() => mockSearchSource), - }, + dataPluginMock = { + search: { + searchSource: { + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, - } as unknown as DiscoverServices); + } as unknown as DataPublicPluginStart; fetchPredecessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size = 10) => { const anchor = { @@ -218,6 +214,7 @@ describe('context predecessors', function () { SortDirection.desc, size, [], + dataPluginMock, true ); }; diff --git a/src/plugins/discover/public/application/context/services/context.successors.test.ts b/src/plugins/discover/public/application/context/services/context.successors.test.ts index dc2e673a8ebca..d9736498bf66e 100644 --- a/src/plugins/discover/public/application/context/services/context.successors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.successors.test.ts @@ -10,10 +10,8 @@ import moment from 'moment'; import { get, last } from 'lodash'; import { DataView, SortDirection } from 'src/plugins/data/common'; import { createContextSearchSourceStub } from './_stubs'; -import { setServices } from '../../../kibana_services'; -import { Query } from '../../../../../data/public'; +import { DataPublicPluginStart, Query } from '../../../../../data/public'; import { fetchSurroundingDocs, SurrDocType } from './context'; -import { DiscoverServices } from '../../../build_services'; import { EsHitRecord, EsHitRecordList } from '../../types'; const MS_PER_DAY = 24 * 60 * 60 * 1000; @@ -35,6 +33,7 @@ describe('context successors', function () { tieBreakerValue: number, size: number ) => Promise; + let dataPluginMock: DataPublicPluginStart; // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockSearchSource: any; const indexPattern = { @@ -48,15 +47,13 @@ describe('context successors', function () { beforeEach(() => { mockSearchSource = createContextSearchSourceStub('@timestamp'); - setServices({ - data: { - search: { - searchSource: { - createEmpty: jest.fn().mockImplementation(() => mockSearchSource), - }, + dataPluginMock = { + search: { + searchSource: { + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, - } as unknown as DiscoverServices); + } as unknown as DataPublicPluginStart; fetchSuccessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { const anchor = { @@ -73,7 +70,8 @@ describe('context successors', function () { tieBreakerField, SortDirection.desc, size, - [] + [], + dataPluginMock ); }; }); @@ -185,15 +183,13 @@ describe('context successors', function () { beforeEach(() => { mockSearchSource = createContextSearchSourceStub('@timestamp'); - setServices({ - data: { - search: { - searchSource: { - createEmpty: jest.fn().mockImplementation(() => mockSearchSource), - }, + dataPluginMock = { + search: { + searchSource: { + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, - } as unknown as DiscoverServices); + } as unknown as DataPublicPluginStart; fetchSuccessors = (timeValIso, timeValNr, tieBreakerField, tieBreakerValue, size) => { const anchor = { @@ -211,6 +207,7 @@ describe('context successors', function () { SortDirection.desc, size, [], + dataPluginMock, true ); }; diff --git a/src/plugins/discover/public/application/context/services/context.ts b/src/plugins/discover/public/application/context/services/context.ts index 7333c5f67e1cc..d5dae272689b9 100644 --- a/src/plugins/discover/public/application/context/services/context.ts +++ b/src/plugins/discover/public/application/context/services/context.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ import { Filter, DataView, ISearchSource } from 'src/plugins/data/common'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { reverseSortDir, SortDirection } from '../utils/sorting'; import { convertIsoToMillis, extractNanos } from '../utils/date_conversion'; import { fetchHitsInInterval } from '../utils/fetch_hits_in_interval'; 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 { EsHitRecord, EsHitRecordList } from '../../types'; export enum SurrDocType { @@ -46,12 +46,12 @@ export async function fetchSurroundingDocs( sortDir: SortDirection, size: number, filters: Filter[], + data: DataPublicPluginStart, useNewFieldsApi?: boolean ): Promise { if (typeof anchor !== 'object' || anchor === null || !size) { return []; } - const { data } = getServices(); const timeField = indexPattern.timeFieldName!; const searchSource = data.search.searchSource.createEmpty(); updateSearchSource(searchSource, indexPattern, filters, Boolean(useNewFieldsApi)); diff --git a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.tsx similarity index 88% rename from src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts rename to src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.tsx index 6d2d1c4519383..b9eb4db79a992 100644 --- a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts +++ b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ +import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; -import { setServices, getServices } from '../../../kibana_services'; import { createFilterManagerMock } from '../../../../../data/public/query/filter_manager/filter_manager.mock'; import { CONTEXT_TIE_BREAKER_FIELDS_SETTING } from '../../../../common'; import { DiscoverServices } from '../../../build_services'; @@ -22,6 +22,7 @@ import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_ import { createContextSearchSourceStub } from '../services/_stubs'; import { DataView } from '../../../../../data_views/common'; import { themeServiceMock } from '../../../../../../core/public/mocks'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; const mockFilterManager = createFilterManagerMock(); @@ -52,7 +53,7 @@ const initDefaults = (tieBreakerFields: string[], indexPatternId = 'the-index-pa const dangerNotification = jest.fn(); const mockSearchSource = createContextSearchSourceStub('timestamp'); - setServices({ + const services = { data: { search: { searchSource: { @@ -74,9 +75,9 @@ const initDefaults = (tieBreakerFields: string[], indexPatternId = 'the-index-pa } }, }, - } as unknown as DiscoverServices); + } as unknown as DiscoverServices; - return { + const props = { dangerNotification, props: { anchorId: 'mock_anchor_id', @@ -86,18 +87,22 @@ const initDefaults = (tieBreakerFields: string[], indexPatternId = 'the-index-pa successorCount: 2, }, useNewFieldsApi: false, - services: getServices(), - } as unknown as ContextAppFetchProps, + } as ContextAppFetchProps, + }; + + return { + result: renderHook(() => useContextAppFetch(props.props), { + wrapper: ({ children }) => ( + {children} + ), + }).result, + dangerNotification, }; }; describe('test useContextAppFetch', () => { it('should fetch all correctly', async () => { - const { props } = initDefaults(['_doc']); - - const { result } = renderHook(() => { - return useContextAppFetch(props); - }); + const { result } = initDefaults(['_doc']); expect(result.current.fetchedState.anchorStatus.value).toBe(LoadingStatus.UNINITIALIZED); expect(result.current.fetchedState.predecessorsStatus.value).toBe(LoadingStatus.UNINITIALIZED); @@ -116,11 +121,7 @@ describe('test useContextAppFetch', () => { }); it('should set anchorStatus to failed when tieBreakingField array is empty', async () => { - const { props } = initDefaults([]); - - const { result } = renderHook(() => { - return useContextAppFetch(props); - }); + const { result } = initDefaults([]); expect(result.current.fetchedState.anchorStatus.value).toBe(LoadingStatus.UNINITIALIZED); @@ -136,11 +137,7 @@ describe('test useContextAppFetch', () => { }); it('should set anchorStatus to failed when invalid indexPatternId provided', async () => { - const { props, dangerNotification } = initDefaults(['_doc'], ''); - - const { result } = renderHook(() => { - return useContextAppFetch(props); - }); + const { result, dangerNotification } = initDefaults(['_doc'], ''); expect(result.current.fetchedState.anchorStatus.value).toBe(LoadingStatus.UNINITIALIZED); @@ -157,11 +154,7 @@ describe('test useContextAppFetch', () => { }); it('should fetch context rows correctly', async () => { - const { props } = initDefaults(['_doc']); - - const { result } = renderHook(() => { - return useContextAppFetch(props); - }); + const { result } = initDefaults(['_doc']); expect(result.current.fetchedState.predecessorsStatus.value).toBe(LoadingStatus.UNINITIALIZED); expect(result.current.fetchedState.successorsStatus.value).toBe(LoadingStatus.UNINITIALIZED); @@ -177,11 +170,7 @@ describe('test useContextAppFetch', () => { }); it('should set context rows statuses to failed when invalid indexPatternId provided', async () => { - const { props, dangerNotification } = initDefaults(['_doc'], ''); - - const { result } = renderHook(() => { - return useContextAppFetch(props); - }); + const { result, dangerNotification } = initDefaults(['_doc'], ''); expect(result.current.fetchedState.predecessorsStatus.value).toBe(LoadingStatus.UNINITIALIZED); expect(result.current.fetchedState.successorsStatus.value).toBe(LoadingStatus.UNINITIALIZED); diff --git a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx index 430fda3c15376..2568a574df25d 100644 --- a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx +++ b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx @@ -8,7 +8,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { CONTEXT_TIE_BREAKER_FIELDS_SETTING } from '../../../../common'; -import { DiscoverServices } from '../../../build_services'; import { fetchAnchor } from '../services/anchor'; import { fetchSurroundingDocs, SurrDocType } from '../services/context'; import { MarkdownSimple, toMountPoint, wrapWithTheme } from '../../../../../kibana_react/public'; @@ -22,6 +21,7 @@ import { import { AppState } from '../services/context_state'; import { getFirstSortableField } from './sorting'; import { EsHitRecord } from '../../types'; +import { useDiscoverServices } from '../../../utils/use_discover_services'; const createError = (statusKey: string, reason: FailureReason, error?: Error) => ({ [statusKey]: { value: LoadingStatus.FAILED, error, reason }, @@ -32,7 +32,6 @@ export interface ContextAppFetchProps { indexPattern: DataView; appState: AppState; useNewFieldsApi: boolean; - services: DiscoverServices; } export function useContextAppFetch({ @@ -40,9 +39,14 @@ export function useContextAppFetch({ indexPattern, appState, useNewFieldsApi, - services, }: ContextAppFetchProps) { - const { uiSettings: config, data, toastNotifications, filterManager, core } = services; + const { + uiSettings: config, + data, + toastNotifications, + filterManager, + core, + } = useDiscoverServices(); const { theme$ } = core.theme; const searchSource = useMemo(() => { @@ -133,6 +137,7 @@ export function useContextAppFetch({ SortDirection.desc, count, filters, + data, useNewFieldsApi ); setState({ [type]: rows, [statusKey]: { value: LoadingStatus.LOADED } }); @@ -156,6 +161,7 @@ export function useContextAppFetch({ toastNotifications, useNewFieldsApi, theme$, + data, ] ); diff --git a/src/plugins/discover/public/application/discover_router.test.tsx b/src/plugins/discover/public/application/discover_router.test.tsx index dad796bc7c5f6..9d8d66f333137 100644 --- a/src/plugins/discover/public/application/discover_router.test.tsx +++ b/src/plugins/discover/public/application/discover_router.test.tsx @@ -12,20 +12,14 @@ import { createSearchSessionMock } from '../__mocks__/search_session'; import { discoverServiceMock as mockDiscoverServices } from '../__mocks__/services'; import { discoverRouter } from './discover_router'; import { DiscoverMainRoute } from './main'; -import { DiscoverMainProps } from './main/discover_main_route'; import { SingleDocRoute } from './doc'; import { ContextAppRoute } from './context'; const pathMap: Record = {}; -let mainRouteProps: DiscoverMainProps; describe('Discover router', () => { beforeAll(() => { const { history } = createSearchSessionMock(); - mainRouteProps = { - history, - services: mockDiscoverServices, - }; const component = shallow(discoverRouter(mockDiscoverServices, history)); component.find(Route).forEach((route) => { const routeProps = route.props() as RouteProps; @@ -39,22 +33,18 @@ describe('Discover router', () => { }); it('should show DiscoverMainRoute component for / route', () => { - expect(pathMap['/']).toMatchObject(); + expect(pathMap['/']).toMatchObject(); }); it('should show DiscoverMainRoute component for /view/:id route', () => { - expect(pathMap['/view/:id']).toMatchObject(); + expect(pathMap['/view/:id']).toMatchObject(); }); it('should show SingleDocRoute component for /doc/:indexPatternId/:index route', () => { - expect(pathMap['/doc/:indexPatternId/:index']).toMatchObject( - - ); + expect(pathMap['/doc/:indexPatternId/:index']).toMatchObject(); }); it('should show ContextAppRoute component for /context/:indexPatternId/:id route', () => { - expect(pathMap['/context/:indexPatternId/:id']).toMatchObject( - - ); + expect(pathMap['/context/:indexPatternId/:id']).toMatchObject(); }); }); diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 66ad0cccd03c7..16ff443d15d24 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -16,41 +16,35 @@ import { SingleDocRoute } from './doc'; import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; -import { DiscoverMainProps } from './main/discover_main_route'; -export const discoverRouter = (services: DiscoverServices, history: History) => { - const mainRouteProps: DiscoverMainProps = { - services, - history, - }; - - return ( - - - - - } - /> - ( - - )} - /> - } - /> - } /> - } /> - - - - - - ); -}; +export const discoverRouter = (services: DiscoverServices, history: History) => ( + + + + + + + + ( + + )} + /> + + + + + + + + + + + + + + +); diff --git a/src/plugins/discover/public/application/doc/components/doc.test.tsx b/src/plugins/discover/public/application/doc/components/doc.test.tsx index 4131a004d299b..f9b024b9c6835 100644 --- a/src/plugins/discover/public/application/doc/components/doc.test.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.test.tsx @@ -15,6 +15,7 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource } from '../../../../common'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; const mockSearchApi = jest.fn(); @@ -23,30 +24,6 @@ jest.mock('../../../kibana_services', () => { let registry: any[] = []; return { - getServices: () => ({ - metadata: { - branch: 'test', - }, - data: { - search: { - search: mockSearchApi, - }, - }, - docLinks: { - links: { - apis: { - indexExists: 'mockUrl', - }, - }, - }, - uiSettings: { - get: (key: string) => { - if (key === mockSearchFieldsFromSource) { - return false; - } - }, - }, - }), getDocViewsRegistry: () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any addDocView(view: any) { @@ -82,8 +59,36 @@ async function mountDoc(update = false) { indexPattern: indexPatternMock, } as DocProps; let comp!: ReactWrapper; + const services = { + metadata: { + branch: 'test', + }, + data: { + search: { + search: mockSearchApi, + }, + }, + docLinks: { + links: { + apis: { + indexExists: 'mockUrl', + }, + }, + }, + uiSettings: { + get: (key: string) => { + if (key === mockSearchFieldsFromSource) { + return false; + } + }, + }, + }; await act(async () => { - comp = mountWithIntl(); + comp = mountWithIntl( + + + + ); if (update) comp.update(); }); if (update) { diff --git a/src/plugins/discover/public/application/doc/components/doc.tsx b/src/plugins/discover/public/application/doc/components/doc.tsx index 59fef7ea0b9fd..e70f66de30244 100644 --- a/src/plugins/discover/public/application/doc/components/doc.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.tsx @@ -10,10 +10,10 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent, EuiPage } from '@elastic/eui'; import { DataView } from 'src/plugins/data/common'; -import { getServices } from '../../../kibana_services'; import { DocViewer } from '../../../services/doc_views/components/doc_viewer'; import { ElasticRequestState } from '../types'; import { useEsDocSearch } from '../../../utils/use_es_doc_search'; +import { useDiscoverServices } from '../../../utils/use_discover_services'; export interface DocProps { /** @@ -37,7 +37,8 @@ export interface DocProps { export function Doc(props: DocProps) { const { indexPattern } = props; const [reqState, hit] = useEsDocSearch(props); - const indexExistsLink = getServices().docLinks.links.apis.indexExists; + const { docLinks } = useDiscoverServices(); + const indexExistsLink = docLinks.links.apis.indexExists; return ( diff --git a/src/plugins/discover/public/application/doc/single_doc_route.tsx b/src/plugins/discover/public/application/doc/single_doc_route.tsx index 0d65bbeb714cf..d11c6bdca76a0 100644 --- a/src/plugins/discover/public/application/doc/single_doc_route.tsx +++ b/src/plugins/discover/public/application/doc/single_doc_route.tsx @@ -14,10 +14,10 @@ import { LoadingIndicator } from '../../components/common/loading_indicator'; import { useIndexPattern } from '../../utils/use_index_pattern'; import { withQueryParams } from '../../utils/with_query_params'; import { useMainRouteBreadcrumb } from '../../utils/use_navigation_props'; -import { DiscoverRouteProps } from '../types'; import { Doc } from './components/doc'; +import { useDiscoverServices } from '../../utils/use_discover_services'; -export interface SingleDocRouteProps extends DiscoverRouteProps { +export interface SingleDocRouteProps { /** * Document id */ @@ -29,8 +29,8 @@ export interface DocUrlParams { index: string; } -const SingleDoc = (props: SingleDocRouteProps) => { - const { id, services } = props; +const SingleDoc = ({ id }: SingleDocRouteProps) => { + const services = useDiscoverServices(); const { chrome, timefilter } = services; const { indexPatternId, index } = useParams(); diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 55407835c0a4b..826a02c29ce1a 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -6,12 +6,11 @@ * Side Public License, v 1. */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; import { discoverRouter } from './discover_router'; import { toMountPoint, wrapWithTheme } from '../../../kibana_react/public'; +import { DiscoverServices } from '../build_services'; -export const renderApp = (element: HTMLElement) => { - const services = getServices(); +export const renderApp = (element: HTMLElement, services: DiscoverServices) => { const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx index 673d831f3fc96..3feb8f2cea6b5 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx @@ -20,10 +20,11 @@ import { FetchStatus } from '../../../types'; import { Chart } from './point_series'; import { DiscoverChart } from './discover_chart'; import { VIEW_MODE } from '../../../../components/view_mode_toggle'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; setHeaderActionMenuMounter(jest.fn()); -function getProps(isTimeBased: boolean = false) { +function mountComponent(isTimeBased: boolean = false) { const searchSourceMock = createSearchSourceMock({}); const services = discoverServiceMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { @@ -84,7 +85,7 @@ function getProps(isTimeBased: boolean = false) { }, }) as DataCharts$; - return { + const props = { isTimeBased, resetSavedSearch: jest.fn(), savedSearch: savedSearchMock, @@ -92,21 +93,26 @@ function getProps(isTimeBased: boolean = false) { savedSearchDataTotalHits$: totalHits$, savedSearchRefetch$: new Subject(), searchSource: searchSourceMock, - services, state: { columns: [] }, stateContainer: {} as GetStateReturn, viewMode: VIEW_MODE.DOCUMENT_LEVEL, setDiscoverViewMode: jest.fn(), }; + + return mountWithIntl( + + + + ); } describe('Discover chart', () => { test('render without timefield', () => { - const component = mountWithIntl(); + const component = mountComponent(); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy(); }); test('render with filefield', () => { - const component = mountWithIntl(); + const component = mountComponent(true); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy(); }); }); diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx index 0c3d83e256525..ab9478a0f334c 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx @@ -21,10 +21,10 @@ import { SavedSearch } from '../../../../services/saved_searches'; import { GetStateReturn } from '../../services/discover_state'; import { DiscoverHistogram } from './histogram'; import { DataCharts$, DataTotalHits$ } from '../../utils/use_saved_search'; -import { DiscoverServices } from '../../../../build_services'; import { useChartPanels } from './use_chart_panels'; import { VIEW_MODE, DocumentViewModeToggle } from '../../../../components/view_mode_toggle'; import { SHOW_FIELD_STATISTICS } from '../../../../../common'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; const DiscoverHistogramMemoized = memo(DiscoverHistogram); export const CHART_HIDDEN_KEY = 'discover:chartHidden'; @@ -34,7 +34,6 @@ export function DiscoverChart({ savedSearch, savedSearchDataChart$, savedSearchDataTotalHits$, - services, stateContainer, isTimeBased, viewMode, @@ -46,7 +45,6 @@ export function DiscoverChart({ savedSearch: SavedSearch; savedSearchDataChart$: DataCharts$; savedSearchDataTotalHits$: DataTotalHits$; - services: DiscoverServices; stateContainer: GetStateReturn; isTimeBased: boolean; viewMode: VIEW_MODE; @@ -54,10 +52,9 @@ export function DiscoverChart({ hideChart?: boolean; interval?: string; }) { + const { uiSettings, data, storage } = useDiscoverServices(); const [showChartOptionsPopover, setShowChartOptionsPopover] = useState(false); - const showViewModeToggle = services.uiSettings.get(SHOW_FIELD_STATISTICS) ?? false; - - const { data, storage } = services; + const showViewModeToggle = uiSettings.get(SHOW_FIELD_STATISTICS) ?? false; const chartRef = useRef<{ element: HTMLElement | null; moveFocus: boolean }>({ element: null, @@ -165,7 +162,6 @@ export function DiscoverChart({ diff --git a/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx b/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx index 8950ac160b634..547c6ffe42f48 100644 --- a/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx @@ -14,6 +14,7 @@ import { Chart } from './point_series'; import { DiscoverHistogram } from './histogram'; import React from 'react'; import * as hooks from '../../utils/use_data_state'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; const chartData = { xAxisOrderedValues: [ @@ -54,7 +55,7 @@ const chartData = { ], } as unknown as Chart; -function getProps(fetchStatus: FetchStatus) { +function mountComponent(fetchStatus: FetchStatus) { const services = discoverServiceMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; @@ -72,11 +73,16 @@ function getProps(fetchStatus: FetchStatus) { const timefilterUpdateHandler = jest.fn(); - return { + const props = { savedSearchData$: charts$, - services, timefilterUpdateHandler, }; + + return mountWithIntl( + + + + ); } describe('Histogram', () => { @@ -90,7 +96,7 @@ describe('Histogram', () => { scale: 1, }, })); - const component = mountWithIntl(); + const component = mountComponent(FetchStatus.COMPLETE); expect(component.find('[data-test-subj="discoverChart"]').exists()).toBe(true); }); @@ -99,7 +105,7 @@ describe('Histogram', () => { fetchStatus: FetchStatus.ERROR, error: new Error('Loading error'), })); - const component = mountWithIntl(); + const component = mountComponent(FetchStatus.ERROR); expect(component.find('[data-test-subj="discoverChart"]').exists()).toBe(false); expect(component.find('.dscHistogram__errorChartContainer').exists()).toBe(true); expect(component.find('.dscHistogram__errorChart__text').get(1).props.children).toBe( @@ -112,7 +118,7 @@ describe('Histogram', () => { fetchStatus: FetchStatus.LOADING, chartData: null, })); - const component = mountWithIntl(); + const component = mountComponent(FetchStatus.LOADING); expect(component.find('[data-test-subj="discoverChart"]').exists()).toBe(true); expect(component.find('.dscChart__loading').exists()).toBe(true); }); diff --git a/src/plugins/discover/public/application/main/components/chart/histogram.tsx b/src/plugins/discover/public/application/main/components/chart/histogram.tsx index d411bb7ddad5c..369513d3b7a31 100644 --- a/src/plugins/discover/public/application/main/components/chart/histogram.tsx +++ b/src/plugins/discover/public/application/main/components/chart/histogram.tsx @@ -34,6 +34,7 @@ import { } from '@elastic/charts'; import { IUiSettingsClient } from 'kibana/public'; import { i18n } from '@kbn/i18n'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { CurrentTime, Endzones, @@ -42,14 +43,12 @@ import { } from '../../../../../../charts/public'; import { DataCharts$, DataChartsMessage } from '../../utils/use_saved_search'; import { FetchStatus } from '../../../types'; -import { DiscoverServices } from '../../../../build_services'; import { useDataState } from '../../utils/use_data_state'; import { LEGACY_TIME_AXIS, MULTILAYER_TIME_AXIS_STYLE } from '../../../../../../charts/common'; export interface DiscoverHistogramProps { savedSearchData$: DataCharts$; timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; - services: DiscoverServices; } function getTimezone(uiSettings: IUiSettingsClient) { @@ -65,14 +64,13 @@ function getTimezone(uiSettings: IUiSettingsClient) { export function DiscoverHistogram({ savedSearchData$, timefilterUpdateHandler, - services, }: DiscoverHistogramProps) { - const chartTheme = services.theme.useChartsTheme(); - const chartBaseTheme = services.theme.useChartsBaseTheme(); + const { data, theme, uiSettings } = useDiscoverServices(); + const chartTheme = theme.useChartsTheme(); + const chartBaseTheme = theme.useChartsBaseTheme(); const dataState: DataChartsMessage = useDataState(savedSearchData$); - const uiSettings = services.uiSettings; const timeZone = getTimezone(uiSettings); const { chartData, bucketInterval, fetchStatus, error } = dataState; @@ -102,7 +100,7 @@ export function DiscoverHistogram({ [timefilterUpdateHandler] ); - const { timefilter } = services.data.query.timefilter; + const { timefilter } = data.query.timefilter; const { from, to } = timefilter.getAbsoluteTime(); const dateFormat = useMemo(() => uiSettings.get('dateFormat'), [uiSettings]); @@ -174,7 +172,6 @@ export function DiscoverHistogram({ return moment(val).format(xAxisFormat); }; - const data = chartData.values; const isDarkMode = uiSettings.get('theme:darkMode'); /* @@ -192,7 +189,7 @@ export function DiscoverHistogram({ const domainStart = domain.min.valueOf(); const domainEnd = domain.max.valueOf(); - const domainMin = Math.min(data[0]?.x, domainStart); + const domainMin = Math.min(chartData.values[0]?.x, domainStart); const domainMax = Math.max(domainEnd - xInterval, lastXValue); const xDomain = { @@ -210,7 +207,7 @@ export function DiscoverHistogram({ type: TooltipType.VerticalCursor, }; - const xAxisFormatter = services.data.fieldFormats.deserialize(chartData.yAxisFormat); + const xAxisFormatter = data.fieldFormats.deserialize(chartData.yAxisFormat); const useLegacyTimeAxis = uiSettings.get(LEGACY_TIME_AXIS, false); @@ -298,7 +295,7 @@ export function DiscoverHistogram({ yScaleType={ScaleType.Linear} xAccessor="x" yAccessors={['y']} - data={data} + data={chartData.values} yNice timeZone={timeZone} name={chartData.yAxisLabel} diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 545ac6a329581..b57c28355626f 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -9,8 +9,8 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import type { Filter } from '@kbn/es-query'; import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DataViewField, DataView, Query } from '../../../../../../data/common'; -import type { DiscoverServices } from '../../../../build_services'; import { EmbeddableInput, EmbeddableOutput, @@ -57,10 +57,6 @@ export interface FieldStatisticsTableProps { * Saved search title */ searchTitle?: string; - /** - * Discover plugin services - */ - services: DiscoverServices; /** * Optional saved search */ @@ -93,7 +89,6 @@ export interface FieldStatisticsTableProps { export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { const { - services, indexPattern, savedSearch, query, @@ -105,7 +100,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { savedSearchRefetch$, searchSessionId, } = props; - const { uiSettings } = services; + const services = useDiscoverServices(); const [embeddable, setEmbeddable] = useState< | ErrorEmbeddable | IEmbeddable @@ -171,7 +166,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { embeddable.reload(); } - }, [showPreviewByDefault, uiSettings, embeddable]); + }, [showPreviewByDefault, embeddable]); useEffect(() => { let unmounted = false; @@ -216,7 +211,7 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { // Clean up embeddable upon unmounting embeddable?.destroy(); }; - }, [embeddable, embeddableRoot, uiSettings, trackUiMetric]); + }, [embeddable, embeddableRoot, trackUiMetric]); return (
- - - ); -} diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/index.ts b/src/plugins/discover/public/application/main/components/field_stats_table/index.ts index 39f3dd81e74e6..b418366481fa0 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/index.ts +++ b/src/plugins/discover/public/application/main/components/field_stats_table/index.ts @@ -7,4 +7,3 @@ */ export { FieldStatisticsTable } from './field_stats_table'; -export { FieldStatsTableSavedSearchEmbeddable } from './field_stats_table_saved_search_embeddable'; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index 45c064d06c51f..5f9d2d41f862d 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -19,15 +19,11 @@ import { FetchStatus } from '../../../types'; import { DiscoverDocuments } from './discover_documents'; import { indexPatternMock } from '../../../../__mocks__/index_pattern'; import { ElasticSearchHit } from 'src/plugins/discover/public/types'; - -jest.mock('../../../../kibana_services', () => ({ - ...jest.requireActual('../../../../kibana_services'), - getServices: () => jest.requireActual('../../../../__mocks__/services').discoverServiceMock, -})); +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; setHeaderActionMenuMounter(jest.fn()); -function getProps(fetchStatus: FetchStatus, hits: ElasticSearchHit[]) { +function mountComponent(fetchStatus: FetchStatus, hits: ElasticSearchHit[]) { const services = discoverServiceMock; services.data.query.timefilter.timefilter.getTime = () => { return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; @@ -38,40 +34,41 @@ function getProps(fetchStatus: FetchStatus, hits: ElasticSearchHit[]) { result: hits, }) as DataDocuments$; - return { + const props = { expandedDoc: undefined, indexPattern: indexPatternMock, onAddFilter: jest.fn(), savedSearch: savedSearchMock, documents$, searchSource: documents$, - services, setExpandedDoc: jest.fn(), state: { columns: [] }, stateContainer: {} as GetStateReturn, navigateTo: jest.fn(), }; + + return mountWithIntl( + + + + ); } describe('Discover documents layout', () => { test('render loading when loading and no documents', () => { - const component = mountWithIntl(); + const component = mountComponent(FetchStatus.LOADING, []); expect(component.find('.dscDocuments__loading').exists()).toBeTruthy(); expect(component.find('.dscTable').exists()).toBeFalsy(); }); test('render complete when loading but documents were already fetched', () => { - const component = mountWithIntl( - - ); + const component = mountComponent(FetchStatus.LOADING, esHits as ElasticSearchHit[]); expect(component.find('.dscDocuments__loading').exists()).toBeFalsy(); expect(component.find('.dscTable').exists()).toBeTruthy(); }); test('render complete', () => { - const component = mountWithIntl( - - ); + const component = mountComponent(FetchStatus.COMPLETE, esHits as ElasticSearchHit[]); expect(component.find('.dscDocuments__loading').exists()).toBeFalsy(); expect(component.find('.dscTable').exists()).toBeTruthy(); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index d2b767ce5bcd8..c955157a9f703 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -14,6 +14,7 @@ import { EuiScreenReaderOnly, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DocViewFilterFn } from '../../../../services/doc_views/doc_views_types'; import { DiscoverGrid } from '../../../../components/discover_grid/discover_grid'; import { FetchStatus } from '../../../types'; @@ -27,7 +28,6 @@ import { useColumns } from '../../../../utils/use_data_grid_columns'; import { DataView } from '../../../../../../data/common'; import { SavedSearch } from '../../../../services/saved_searches'; import { DataDocumentsMsg, DataDocuments$ } from '../../utils/use_saved_search'; -import { DiscoverServices } from '../../../../build_services'; import { AppState, GetStateReturn } from '../../services/discover_state'; import { useDataState } from '../../utils/use_data_state'; import { DocTableInfinite } from '../../../../components/doc_table/doc_table_infinite'; @@ -43,7 +43,6 @@ function DiscoverDocumentsComponent({ indexPattern, onAddFilter, savedSearch, - services, setExpandedDoc, state, stateContainer, @@ -54,12 +53,11 @@ function DiscoverDocumentsComponent({ navigateTo: (url: string) => void; onAddFilter: DocViewFilterFn; savedSearch: SavedSearch; - services: DiscoverServices; setExpandedDoc: (doc?: ElasticSearchHit) => void; state: AppState; stateContainer: GetStateReturn; }) { - const { capabilities, indexPatterns, uiSettings } = services; + const { capabilities, indexPatterns, uiSettings } = useDiscoverServices(); const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); const isLegacy = useMemo(() => uiSettings.get(DOC_TABLE_LEGACY), [uiSettings]); @@ -160,7 +158,6 @@ function DiscoverDocumentsComponent({ searchTitle={savedSearch.title} setExpandedDoc={setExpandedDoc} showTimeCol={showTimeCol} - services={services} settings={state.grid} onAddColumn={onAddColumn} onFilter={onAddFilter as DocViewFilterFn} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx index c8cfd3603c9f0..b258987e3ea30 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -32,23 +32,13 @@ import { RequestAdapter } from '../../../../../../inspector'; import { Chart } from '../chart/point_series'; import { DiscoverSidebar } from '../sidebar/discover_sidebar'; import { ElasticSearchHit } from '../../../../types'; - -jest.mock('../../../../kibana_services', () => ({ - ...jest.requireActual('../../../../kibana_services'), - getServices: () => ({ - fieldFormats: { - getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), - getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), - }, - uiSettings: { - get: jest.fn((key: string) => key === 'discover:maxDocFieldsDisplayed' && 50), - }, - }), -})); +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; +import { FieldFormatsStart } from '../../../../../../field_formats/public'; +import { IUiSettingsClient } from 'kibana/public'; setHeaderActionMenuMounter(jest.fn()); -function getProps(indexPattern: DataView, wasSidebarClosed?: boolean): DiscoverLayoutProps { +function mountComponent(indexPattern: DataView, prevSidebarClosed?: boolean) { const searchSourceMock = createSearchSourceMock({}); const services = discoverServiceMock; services.data.query.timefilter.timefilter.getAbsoluteTime = () => { @@ -56,9 +46,17 @@ function getProps(indexPattern: DataView, wasSidebarClosed?: boolean): DiscoverL }; services.storage.get = (key: string) => { if (key === SIDEBAR_CLOSED_KEY) { - return wasSidebarClosed; + return prevSidebarClosed; } }; + services.fieldFormats = { + getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), + getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), + } as unknown as FieldFormatsStart; + services.uiSettings = { + ...services.uiSettings, + get: jest.fn((key: string) => key === 'discover:maxDocFieldsDisplayed' && 50), + } as unknown as IUiSettingsClient; const indexPatternList = [indexPattern].map((ip) => { return { ...ip, ...{ attributes: { title: ip.title } } }; @@ -135,7 +133,7 @@ function getProps(indexPattern: DataView, wasSidebarClosed?: boolean): DiscoverL charts$, }; - return { + const props = { indexPattern, indexPatternList, inspectorAdapters: { requests: new RequestAdapter() }, @@ -147,45 +145,42 @@ function getProps(indexPattern: DataView, wasSidebarClosed?: boolean): DiscoverL savedSearchData$, savedSearchRefetch$: new Subject(), searchSource: searchSourceMock, - services, state: { columns: [] }, stateContainer: {} as GetStateReturn, setExpandedDoc: jest.fn(), }; + + return mountWithIntl( + + + + ); } describe('Discover component', () => { test('selected index pattern without time field displays no chart toggle', () => { - const component = mountWithIntl(); + const component = mountComponent(indexPatternMock); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy(); }); test('selected index pattern with time field displays chart toggle', () => { - const component = mountWithIntl( - - ); + const component = mountComponent(indexPatternWithTimefieldMock); expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy(); }); describe('sidebar', () => { test('should be opened if discover:sidebarClosed was not set', () => { - const component = mountWithIntl( - - ); + const component = mountComponent(indexPatternWithTimefieldMock, undefined); expect(component.find(DiscoverSidebar).length).toBe(1); }); test('should be opened if discover:sidebarClosed is false', () => { - const component = mountWithIntl( - - ); + const component = mountComponent(indexPatternWithTimefieldMock, false); expect(component.find(DiscoverSidebar).length).toBe(1); }); test('should be closed if discover:sidebarClosed is true', () => { - const component = mountWithIntl( - - ); + const component = mountComponent(indexPatternWithTimefieldMock, true); expect(component.find(DiscoverSidebar).length).toBe(0); }); }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index e98605326cc32..5601596a4d73b 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -21,6 +21,7 @@ import { import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import classNames from 'classnames'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DiscoverNoResults } from '../no_results'; import { LoadingSpinner } from '../loading_spinner/loading_spinner'; import { esFilters } from '../../../../../../data/public'; @@ -73,7 +74,6 @@ export function DiscoverLayout({ savedSearchData$, savedSearch, searchSource, - services, state, stateContainer, }: DiscoverLayoutProps) { @@ -87,7 +87,8 @@ export function DiscoverLayout({ storage, history, spaces, - } = services; + inspector, + } = useDiscoverServices(); const { main$, charts$, totalHits$ } = savedSearchData$; const [inspectorSession, setInspectorSession] = useState(undefined); @@ -142,11 +143,11 @@ export function DiscoverLayout({ const onOpenInspector = useCallback(() => { // prevent overlapping setExpandedDoc(undefined); - const session = services.inspector.open(inspectorAdapters, { + const session = inspector.open(inspectorAdapters, { title: savedSearch.title, }); setInspectorSession(session); - }, [setExpandedDoc, inspectorAdapters, savedSearch, services.inspector]); + }, [setExpandedDoc, inspectorAdapters, savedSearch, inspector]); useEffect(() => { return () => { @@ -214,7 +215,6 @@ export function DiscoverLayout({ savedQuery={state.savedQuery} savedSearch={savedSearch} searchSource={searchSource} - services={services} stateContainer={stateContainer} updateQuery={onUpdateQuery} resetSavedSearch={resetSavedSearch} @@ -239,7 +239,6 @@ export function DiscoverLayout({ onRemoveField={onRemoveColumn} onChangeIndexPattern={onChangeIndexPattern} selectedIndexPattern={indexPattern} - services={services} state={state} isClosed={isSidebarClosed} trackUiMetric={trackUiMetric} @@ -308,7 +307,6 @@ export function DiscoverLayout({ savedSearch={savedSearch} savedSearchDataChart$={charts$} savedSearchDataTotalHits$={totalHits$} - services={services} stateContainer={stateContainer} isTimeBased={isTimeBased} viewMode={viewMode} @@ -326,7 +324,6 @@ export function DiscoverLayout({ navigateTo={navigateTo} onAddFilter={onAddFilter as DocViewFilterFn} savedSearch={savedSearch} - services={services} setExpandedDoc={setExpandedDoc} state={state} stateContainer={stateContainer} @@ -334,7 +331,6 @@ export function DiscoverLayout({ ) : ( { - return { - getServices: () => ({ - docLinks: { - links: { - query: { - luceneQuerySyntax: 'documentation-link', - }, - }, - }, - }), - }; -}); +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; beforeEach(() => { jest.clearAllMocks(); }); function mountAndFindSubjects(props: Omit) { - const component = mountWithIntl( {}} {...props} />); + const services = { + docLinks: { + links: { + query: { + luceneQuerySyntax: 'documentation-link', + }, + }, + }, + }; + const component = mountWithIntl( + + {}} {...props} /> + + ); return { mainMsg: findTestSubject(component, 'discoverNoResults').exists(), timeFieldMsg: findTestSubject(component, 'discoverNoResultsTimefilter').exists(), diff --git a/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap b/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap index afadc293f6420..94aa55ff23853 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap +++ b/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Discover DataView Management renders correctly 1`] = ` - - + -
-
+ + } + closePopover={[Function]} + data-test-subj="discover-addRuntimeField-popover" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + > +
+
- - + type="boxesHorizontal" + > +
-
- - + + + `; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index c68339575023a..758738a6cbc56 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -13,6 +13,7 @@ import { mountWithIntl } from '@kbn/test/jest'; import { DiscoverField } from './discover_field'; import { DataViewField } from '../../../../../../data/common'; import { stubIndexPattern } from '../../../../../../data/common/stubs'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; jest.mock('../../../../kibana_services', () => ({ getUiActions: jest.fn(() => { @@ -20,25 +21,6 @@ jest.mock('../../../../kibana_services', () => ({ getTriggerCompatibleActions: jest.fn(() => []), }; }), - getServices: () => ({ - history: () => ({ - location: { - search: '', - }, - }), - capabilities: { - visualize: { - show: true, - }, - }, - uiSettings: { - get: (key: string) => { - if (key === 'fields:popularLimit') { - return 5; - } - }, - }, - }), })); function getComponent({ @@ -73,7 +55,30 @@ function getComponent({ showDetails, selected, }; - const comp = mountWithIntl(); + const services = { + history: () => ({ + location: { + search: '', + }, + }), + capabilities: { + visualize: { + show: true, + }, + }, + uiSettings: { + get: (key: string) => { + if (key === 'fields:popularLimit') { + return 5; + } + }, + }, + }; + const comp = mountWithIntl( + + + + ); return { comp, props }; } diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx index d44091c27d297..8ab82924a6c23 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx @@ -12,6 +12,7 @@ import { EuiContextMenuPanel, EuiPopover, EuiContextMenuItem } from '@elastic/eu import { DiscoverServices } from '../../../../build_services'; import { DiscoverIndexPatternManagement } from './discover_index_pattern_management'; import { stubLogstashIndexPattern } from '../../../../../../data/common/stubs'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; const mockServices = { history: () => ({ @@ -56,12 +57,13 @@ describe('Discover DataView Management', () => { const mountComponent = () => { return mountWithIntl( - + + + ); }; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx index ce201f6ed3ae5..0655357d55983 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DiscoverServices } from '../../../../build_services'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DataView } from '../../../../../../data/common'; export interface DiscoverIndexPatternManagementProps { @@ -17,10 +17,6 @@ export interface DiscoverIndexPatternManagementProps { * Currently selected index pattern */ selectedIndexPattern?: DataView; - /** - * Discover plugin services; - */ - services: DiscoverServices; /** * Read from the Fields API */ @@ -33,7 +29,7 @@ export interface DiscoverIndexPatternManagementProps { } export function DiscoverIndexPatternManagement(props: DiscoverIndexPatternManagementProps) { - const { dataViewFieldEditor, core } = props.services; + const { dataViewFieldEditor, core } = useDiscoverServices(); const { useNewFieldsApi, selectedIndexPattern, editField } = props; const dataViewEditPermission = dataViewFieldEditor?.userPermissions.editIndexPattern(); const canEditDataViewField = !!dataViewEditPermission && useNewFieldsApi; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx index 95c167524af48..e236d7e8a1b89 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx @@ -23,10 +23,7 @@ import { discoverServiceMock as mockDiscoverServices } from '../../../../__mocks import { stubLogstashIndexPattern } from '../../../../../../data/common/stubs'; import { VIEW_MODE } from '../../../../components/view_mode_toggle'; import { ElasticSearchHit } from '../../../../types'; - -jest.mock('../../../../kibana_services', () => ({ - getServices: () => mockDiscoverServices, -})); +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; function getCompProps(): DiscoverSidebarProps { const indexPattern = stubLogstashIndexPattern; @@ -59,7 +56,6 @@ function getCompProps(): DiscoverSidebarProps { onAddField: jest.fn(), onRemoveField: jest.fn(), selectedIndexPattern: indexPattern, - services: mockDiscoverServices, state: {}, trackUiMetric: jest.fn(), fieldFilter: getDefaultFieldFilter(), @@ -76,7 +72,11 @@ describe('discover sidebar', function () { beforeAll(() => { props = getCompProps(); - comp = mountWithIntl(); + comp = mountWithIntl( + + + + ); }); it('should have Selected Fields and Available Fields with Popular Fields sections', function () { diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx index fd5c838c42f91..087a5a6ae312b 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx @@ -25,6 +25,7 @@ import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; import { isEqual, sortBy } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DiscoverField } from './discover_field'; import { DiscoverIndexPattern } from './discover_index_pattern'; import { DiscoverFieldSearch } from './discover_field_search'; @@ -93,7 +94,6 @@ export function DiscoverSidebarComponent({ onAddFilter, onRemoveField, selectedIndexPattern, - services, setFieldFilter, trackUiMetric, useNewFieldsApi = false, @@ -105,9 +105,9 @@ export function DiscoverSidebarComponent({ editField, viewMode, }: DiscoverSidebarProps) { + const { uiSettings, dataViewFieldEditor } = useDiscoverServices(); const [fields, setFields] = useState(null); - const { dataViewFieldEditor } = services; const dataViewFieldEditPermission = dataViewFieldEditor?.userPermissions.editIndexPattern(); const canEditDataViewField = !!dataViewFieldEditPermission && useNewFieldsApi; const [scrollContainer, setScrollContainer] = useState(null); @@ -138,10 +138,7 @@ export function DiscoverSidebarComponent({ [documents, columns, selectedIndexPattern] ); - const popularLimit = useMemo( - () => services.uiSettings.get(FIELDS_LIMIT_SETTING), - [services.uiSettings] - ); + const popularLimit = useMemo(() => uiSettings.get(FIELDS_LIMIT_SETTING), [uiSettings]); const { selected: selectedFields, @@ -299,7 +296,6 @@ export function DiscoverSidebarComponent({ ({ @@ -62,7 +63,6 @@ jest.mock('../../../../kibana_services', () => ({ getTriggerCompatibleActions: jest.fn(() => []), }; }), - getServices: () => mockServices, })); jest.mock('../../utils/calc_field_counts', () => ({ @@ -100,7 +100,6 @@ function getCompProps(): DiscoverSidebarResponsiveProps { onAddField: jest.fn(), onRemoveField: jest.fn(), selectedIndexPattern: indexPattern, - services: mockServices, state: {}, trackUiMetric: jest.fn(), onEditRuntimeField: jest.fn(), @@ -114,7 +113,11 @@ describe('discover responsive sidebar', function () { beforeAll(() => { props = getCompProps(); - comp = mountWithIntl(); + comp = mountWithIntl( + + + + ); }); it('should have Selected Fields and Available Fields with Popular Fields sections', function () { diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index df79b4a03578e..abc59ff282863 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -26,12 +26,12 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DiscoverIndexPattern } from './discover_index_pattern'; import { DataViewField, DataView, DataViewAttributes } from '../../../../../../data/common'; import { SavedObject } from '../../../../../../../core/types'; import { getDefaultFieldFilter } from './lib/field_filter'; import { DiscoverSidebar } from './discover_sidebar'; -import { DiscoverServices } from '../../../../build_services'; import { AppState } from '../../services/discover_state'; import { DiscoverIndexPatternManagement } from './discover_index_pattern_management'; import { DataDocuments$ } from '../../utils/use_saved_search'; @@ -80,10 +80,6 @@ export interface DiscoverSidebarResponsiveProps { * Currently selected index pattern */ selectedIndexPattern?: DataView; - /** - * Discover plugin services; - */ - services: DiscoverServices; /** * Discover App state */ @@ -118,6 +114,7 @@ export interface DiscoverSidebarResponsiveProps { * Mobile: Index pattern selector is visible and a button to trigger a flyout with all elements */ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) { + const services = useDiscoverServices(); const { selectedIndexPattern, onEditRuntimeField, useNewFieldsApi, onChangeIndexPattern } = props; const [fieldFilter, setFieldFilter] = useState(getDefaultFieldFilter()); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); @@ -170,7 +167,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) setIsFlyoutVisible(false); }, []); - const { dataViewFieldEditor } = props.services; + const { dataViewFieldEditor } = services; const editField = useCallback( (fieldName?: string) => { @@ -244,7 +241,6 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) ({ + ...jest.requireActual('../../../../../../kibana_react/public'), + useKibana: () => ({ + services: jest.requireActual('../../../../__mocks__/services').discoverServiceMock, + }), +})); + function getProps(savePermissions = true): DiscoverTopNavProps { discoverServiceMock.capabilities.discover!.save = savePermissions; @@ -27,7 +34,6 @@ function getProps(savePermissions = true): DiscoverTopNavProps { indexPattern: indexPatternMock, savedSearch: savedSearchMock, navigateTo: jest.fn(), - services: discoverServiceMock, query: {} as Query, savedQuery: '', updateQuery: jest.fn(), diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 2e8261ce165da..63e75c74af795 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -7,6 +7,7 @@ */ import React, { useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DiscoverLayoutProps } from '../layout/types'; import { getTopNavLinks } from './get_top_nav_links'; import { Query, TimeRange } from '../../../../../../data/common/query'; @@ -16,7 +17,7 @@ import { DataViewType } from '../../../../../../data_views/common'; export type DiscoverTopNavProps = Pick< DiscoverLayoutProps, - 'indexPattern' | 'navigateTo' | 'savedSearch' | 'services' | 'searchSource' + 'indexPattern' | 'navigateTo' | 'savedSearch' | 'searchSource' > & { onOpenInspector: () => void; query?: Query; @@ -36,7 +37,6 @@ export const DiscoverTopNav = ({ searchSource, navigateTo, savedSearch, - services, resetSavedSearch, }: DiscoverTopNavProps) => { const history = useHistory(); @@ -44,6 +44,7 @@ export const DiscoverTopNav = ({ () => indexPattern.isTimeBased() && indexPattern.type !== DataViewType.ROLLUP, [indexPattern] ); + const services = useDiscoverServices(); const { TopNavMenu } = services.navigation.ui; const onOpenSavedSearch = useCallback( diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts index 8c7d5700b3d87..0a8bcae983d35 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts @@ -53,6 +53,7 @@ export const getTopNavLinks = ({ I18nContext: services.core.i18n.Context, anchorElement, theme$: services.core.theme.theme$, + services, }), testId: 'discoverOptionsButton', }; @@ -97,6 +98,7 @@ export const getTopNavLinks = ({ onOpenSavedSearch, I18nContext: services.core.i18n.Context, theme$: services.core.theme.theme$, + services, }), }; diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx index 8363bfdc57616..c2059915b2af8 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx @@ -9,41 +9,30 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test/jest'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { getServices } from '../../../../kibana_services'; - -jest.mock('../../../../kibana_services', () => { - const mockUiSettings = new Map(); - return { - getServices: () => ({ - core: { - uiSettings: { - get: (key: string) => { - return mockUiSettings.get(key); - }, - set: (key: string, value: boolean) => { - mockUiSettings.set(key, value); - }, - }, - }, - addBasePath: (path: string) => path, - }), - }; -}); import { OptionsPopover } from './open_options_popover'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; test('should display the correct text if datagrid is selected', () => { const element = document.createElement('div'); - const component = mountWithIntl(); + const component = mountWithIntl( + '', core: { uiSettings: { get: () => false } } }} + > + + + ); expect(findTestSubject(component, 'docTableMode').text()).toBe('Document Explorer'); }); test('should display the correct text if legacy table is selected', () => { - const { - core: { uiSettings }, - } = getServices(); - uiSettings.set('doc_table:legacy', true); const element = document.createElement('div'); - const component = mountWithIntl(); + const component = mountWithIntl( + '', core: { uiSettings: { get: () => true } } }} + > + + + ); expect(findTestSubject(component, 'docTableMode').text()).toBe('Classic'); }); diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx index ea0cd804efec0..b52aa21414664 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx @@ -23,9 +23,10 @@ import { } from '@elastic/eui'; import './open_options_popover.scss'; import { Observable } from 'rxjs'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; +import { DiscoverServices } from '../../../../build_services'; import { DOC_TABLE_LEGACY } from '../../../../../common'; -import { getServices } from '../../../../kibana_services'; -import { KibanaThemeProvider } from '../../../../../../kibana_react/public'; +import { KibanaContextProvider, KibanaThemeProvider } from '../../../../../../kibana_react/public'; const container = document.createElement('div'); let isOpen = false; @@ -39,7 +40,7 @@ export function OptionsPopover(props: OptionsPopoverProps) { const { core: { uiSettings }, addBasePath, - } = getServices(); + } = useDiscoverServices(); const isLegacy = uiSettings.get(DOC_TABLE_LEGACY); const mode = isLegacy @@ -128,10 +129,12 @@ export function openOptionsPopover({ I18nContext, anchorElement, theme$, + services, }: { I18nContext: I18nStart['Context']; anchorElement: HTMLElement; theme$: Observable; + services: DiscoverServices; }) { if (isOpen) { onClose(); @@ -143,9 +146,11 @@ export function openOptionsPopover({ const element = ( - - - + + + + + ); ReactDOM.render(element, container); diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.test.tsx index 80c70d9b1aff5..ad4c356ce63e0 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.test.tsx @@ -9,39 +9,37 @@ import React from 'react'; import { shallow } from 'enzyme'; -const mockCapabilities = jest.fn().mockReturnValue({ - savedObjectsManagement: { - edit: true, - }, -}); - -jest.mock('../../../../kibana_services', () => { - return { - getServices: () => ({ - core: { uiSettings: {}, savedObjects: {} }, - addBasePath: (path: string) => path, - capabilities: mockCapabilities(), - }), - }; -}); +describe('OpenSearchPanel', () => { + beforeEach(() => { + jest.resetModules(); + }); -import { OpenSearchPanel } from './open_search_panel'; + test('render', async () => { + jest.doMock('../../../../utils/use_discover_services', () => ({ + useDiscoverServices: jest.fn().mockImplementation(() => ({ + core: { uiSettings: {}, savedObjects: {} }, + addBasePath: (path: string) => path, + capabilities: { savedObjectsManagement: { edit: true } }, + })), + })); + const { OpenSearchPanel } = await import('./open_search_panel'); -describe('OpenSearchPanel', () => { - test('render', () => { const component = shallow( ); expect(component).toMatchSnapshot(); }); - test('should not render manage searches button without permissions', () => { - mockCapabilities.mockReturnValue({ - savedObjectsManagement: { - edit: false, - delete: false, - }, - }); + test('should not render manage searches button without permissions', async () => { + jest.doMock('../../../../utils/use_discover_services', () => ({ + useDiscoverServices: jest.fn().mockImplementation(() => ({ + core: { uiSettings: {}, savedObjects: {} }, + addBasePath: (path: string) => path, + capabilities: { savedObjectsManagement: { edit: false, delete: false } }, + })), + })); + const { OpenSearchPanel } = await import('./open_search_panel'); + const component = shallow( ); diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx index ce6d151261243..3c972a3f4bb98 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_search_panel.tsx @@ -21,7 +21,7 @@ import { EuiTitle, } from '@elastic/eui'; import { SavedObjectFinderUi } from '../../../../../../saved_objects/public'; -import { getServices } from '../../../../kibana_services'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; const SEARCH_OBJECT_TYPE = 'search'; @@ -35,7 +35,7 @@ export function OpenSearchPanel(props: OpenSearchPanelProps) { core: { uiSettings, savedObjects }, addBasePath, capabilities, - } = getServices(); + } = useDiscoverServices(); const hasSavedObjectPermission = capabilities.savedObjectsManagement?.edit || capabilities.savedObjectsManagement?.delete; diff --git a/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx b/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx index d506de357675a..6ba1ffd15130a 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx @@ -10,8 +10,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { CoreTheme, I18nStart } from 'kibana/public'; import { Observable } from 'rxjs'; +import { DiscoverServices } from '../../../../build_services'; import { OpenSearchPanel } from './open_search_panel'; -import { KibanaThemeProvider } from '../../../../../../kibana_react/public'; +import { KibanaContextProvider, KibanaThemeProvider } from '../../../../../../kibana_react/public'; let isOpen = false; @@ -19,10 +20,12 @@ export function showOpenSearchPanel({ I18nContext, onOpenSavedSearch, theme$, + services, }: { I18nContext: I18nStart['Context']; onOpenSavedSearch: (id: string) => void; theme$: Observable; + services: DiscoverServices; }) { if (isOpen) { return; @@ -39,9 +42,11 @@ export function showOpenSearchPanel({ document.body.appendChild(container); const element = ( - - - + + + + + ); ReactDOM.render(element, container); diff --git a/src/plugins/discover/public/application/main/discover_main_app.test.tsx b/src/plugins/discover/public/application/main/discover_main_app.test.tsx index cb2bad306f43f..d1699900b1498 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.test.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.test.tsx @@ -9,32 +9,38 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test/jest'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { DiscoverMainApp } from './discover_main_app'; -import { discoverServiceMock } from '../../__mocks__/services'; import { savedSearchMock } from '../../__mocks__/saved_search'; -import { createSearchSessionMock } from '../../__mocks__/search_session'; import { SavedObject } from '../../../../../core/types'; import { IndexPatternAttributes } from '../../../../data/common'; import { setHeaderActionMenuMounter } from '../../kibana_services'; import { findTestSubject } from '@elastic/eui/lib/test'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; +import { discoverServiceMock } from '../../__mocks__/services'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; setHeaderActionMenuMounter(jest.fn()); describe('DiscoverMainApp', () => { test('renders', () => { - const { history } = createSearchSessionMock(); const indexPatternList = [indexPatternMock].map((ip) => { return { ...ip, ...{ attributes: { title: ip.title } } }; }) as unknown as Array>; - const props = { indexPatternList, - services: discoverServiceMock, savedSearch: savedSearchMock, - navigateTo: jest.fn(), - history, }; + const history = createMemoryHistory({ + initialEntries: ['/'], + }); - const component = mountWithIntl(); + const component = mountWithIntl( + + + + + + ); expect(findTestSubject(component, 'indexPattern-switch-link').text()).toBe( indexPatternMock.title diff --git a/src/plugins/discover/public/application/main/discover_main_app.tsx b/src/plugins/discover/public/application/main/discover_main_app.tsx index 1ef6641e9bc72..846a1fe33c826 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.tsx @@ -6,32 +6,24 @@ * Side Public License, v 1. */ import React, { useCallback, useEffect, useState } from 'react'; -import { History } from 'history'; +import { useHistory } from 'react-router-dom'; import { DiscoverLayout } from './components/layout'; import { setBreadcrumbsTitle } from '../../utils/breadcrumbs'; import { addHelpMenuToAppChrome } from '../../components/help_menu/help_menu_util'; import { useDiscoverState } from './utils/use_discover_state'; import { useUrl } from './utils/use_url'; import { IndexPatternAttributes, SavedObject } from '../../../../data/common'; -import { DiscoverServices } from '../../build_services'; import { SavedSearch } from '../../services/saved_searches'; import { ElasticSearchHit } from '../../types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; const DiscoverLayoutMemoized = React.memo(DiscoverLayout); export interface DiscoverMainProps { - /** - * Instance of browser history - */ - history: History; /** * List of available index patterns */ indexPatternList: Array>; - /** - * Kibana core services used by discover - */ - services: DiscoverServices; /** * Current instance of SavedSearch */ @@ -39,8 +31,10 @@ export interface DiscoverMainProps { } export function DiscoverMainApp(props: DiscoverMainProps) { - const { savedSearch, services, history, indexPatternList } = props; + const { savedSearch, indexPatternList } = props; + const services = useDiscoverServices(); const { chrome, docLinks, uiSettings: config, data } = services; + const history = useHistory(); const [expandedDoc, setExpandedDoc] = useState(undefined); const navigateTo = useCallback( (path: string) => { @@ -113,7 +107,6 @@ export function DiscoverMainApp(props: DiscoverMainProps) { savedSearchData$={data$} savedSearchRefetch$={refetch$} searchSource={searchSource} - services={services} state={state} stateContainer={stateContainer} /> diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index f1d7cc2385cd0..d5950085b94c7 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -6,8 +6,7 @@ * Side Public License, v 1. */ import React, { useEffect, useState, memo, useCallback } from 'react'; -import { History } from 'history'; -import { useParams } from 'react-router-dom'; +import { useParams, useHistory } from 'react-router-dom'; import { IndexPatternAttributes, ISearchSource, SavedObject } from 'src/plugins/data/common'; import { @@ -23,23 +22,18 @@ import { redirectWhenMissing } from '../../../../kibana_utils/public'; import { DataViewSavedObjectConflictError } from '../../../../data_views/common'; import { LoadingIndicator } from '../../components/common/loading_indicator'; import { DiscoverError } from '../../components/common/error_alert'; -import { DiscoverRouteProps } from '../types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; import { getUrlTracker } from '../../kibana_services'; const DiscoverMainAppMemoized = memo(DiscoverMainApp); -export interface DiscoverMainProps extends DiscoverRouteProps { - /** - * Instance of browser history - */ - history: History; -} - interface DiscoverLandingParams { id: string; } -export function DiscoverMainRoute({ services, history }: DiscoverMainProps) { +export function DiscoverMainRoute() { + const history = useHistory(); + const services = useDiscoverServices(); const { core, chrome, @@ -178,12 +172,5 @@ export function DiscoverMainRoute({ services, history }: DiscoverMainProps) { return ; } - return ( - - ); + return ; } diff --git a/src/plugins/discover/public/application/not_found/not_found_route.tsx b/src/plugins/discover/public/application/not_found/not_found_route.tsx index 7b42e85584428..28f525039dd1e 100644 --- a/src/plugins/discover/public/application/not_found/not_found_route.tsx +++ b/src/plugins/discover/public/application/not_found/not_found_route.tsx @@ -11,19 +11,13 @@ import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { Redirect } from 'react-router-dom'; import { toMountPoint, wrapWithTheme } from '../../../../kibana_react/public'; -import { DiscoverServices } from '../../build_services'; import { getUrlTracker } from '../../kibana_services'; +import { useDiscoverServices } from '../../utils/use_discover_services'; -export interface NotFoundRouteProps { - /** - * Kibana core services used by discover - */ - services: DiscoverServices; -} let bannerId: string | undefined; -export function NotFoundRoute(props: NotFoundRouteProps) { - const { services } = props; +export function NotFoundRoute() { + const services = useDiscoverServices(); const { urlForwarding, core, history } = services; const currentLocation = history().location.pathname; diff --git a/src/plugins/discover/public/application/types.ts b/src/plugins/discover/public/application/types.ts index f33b8bb22b58c..f04f3bf77c2f9 100644 --- a/src/plugins/discover/public/application/types.ts +++ b/src/plugins/discover/public/application/types.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { DiscoverServices } from '../build_services'; export enum FetchStatus { UNINITIALIZED = 'uninitialized', @@ -25,10 +24,3 @@ export type EsHitRecord = Required< isAnchor?: boolean; }; export type EsHitRecordList = EsHitRecord[]; - -export interface DiscoverRouteProps { - /** - * Kibana core services used by discover - */ - services: DiscoverServices; -} diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index b77228e309286..393893432538b 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -7,6 +7,7 @@ */ import { History } from 'history'; +import { memoize } from 'lodash'; import { Capabilities, @@ -72,7 +73,7 @@ export interface DiscoverServices { spaces?: SpacesApi; } -export function buildServices( +export const buildServices = memoize(function ( core: CoreStart, plugins: DiscoverStartPlugins, context: PluginInitializerContext @@ -109,4 +110,4 @@ export function buildServices( http: core.http, spaces: plugins.spaces, }; -} +}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx index 11f32890f29ea..c4ef4ffef3234 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx @@ -14,20 +14,12 @@ import { esHits } from '../../__mocks__/es_hits'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { mountWithIntl } from '@kbn/test/jest'; import { DiscoverGrid, DiscoverGridProps } from './discover_grid'; -import { uiSettingsMock } from '../../__mocks__/ui_settings'; -import { DiscoverServices } from '../../build_services'; import { getDocId } from './discover_grid_document_selection'; import { ElasticSearchHit } from '../../types'; - -jest.mock('../../kibana_services', () => ({ - ...jest.requireActual('../../kibana_services'), - getServices: () => jest.requireActual('../../__mocks__/services').discoverServiceMock, -})); +import { KibanaContextProvider } from '../../../../kibana_react/public'; +import { discoverServiceMock } from '../../__mocks__/services'; function getProps() { - const servicesMock = { - uiSettings: uiSettingsMock, - } as DiscoverServices; return { ariaLabelledBy: '', columns: [], @@ -44,7 +36,6 @@ function getProps() { sampleSize: 30, searchDescription: '', searchTitle: '', - services: servicesMock, setExpandedDoc: jest.fn(), settings: {}, showTimeCol: true, @@ -54,7 +45,13 @@ function getProps() { } function getComponent() { - return mountWithIntl(); + const Proxy = (props: DiscoverGridProps) => ( + + + + ); + + return mountWithIntl(); } function getSelectedDocNr(component: ReactWrapper) { diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 93cbbb924f40b..4307afbeb9e38 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -39,7 +39,6 @@ import { pageSizeArr, toolbarVisibility as toolbarVisibilityDefaults, } from './constants'; -import { DiscoverServices } from '../../build_services'; import { getDisplayedColumns } from '../../utils/columns'; import { DOC_HIDE_TIME_COLUMN_SETTING, @@ -50,6 +49,7 @@ import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_docume import { SortPairArr } from '../doc_table/lib/get_sort'; import { getFieldsToShow } from '../../utils/get_fields_to_show'; import { ElasticSearchHit } from '../../types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; interface SortObj { id: string; @@ -130,10 +130,6 @@ export interface DiscoverGridProps { * Saved search title */ searchTitle?: string; - /** - * Discover plugin services - */ - services: DiscoverServices; /** * Determines whether the time columns should be displayed (legacy settings) */ @@ -182,7 +178,6 @@ export const DiscoverGrid = ({ sampleSize, searchDescription, searchTitle, - services, setExpandedDoc, settings, showTimeCol, @@ -193,6 +188,7 @@ export const DiscoverGrid = ({ controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, className, }: DiscoverGridProps) => { + const services = useDiscoverServices(); const [selectedDocs, setSelectedDocs] = useState([]); const [isFilterActive, setIsFilterActive] = useState(false); const displayedColumns = getDisplayedColumns(columns, indexPattern); @@ -481,7 +477,6 @@ export const DiscoverGrid = ({ onAddColumn={onAddColumn} onClose={() => setExpandedDoc(undefined)} setExpandedDoc={setExpandedDoc} - services={services} /> )} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx index 64e97b824a2f9..a6c5ecdcdf35c 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test/jest'; -import { DiscoverGridFlyout } from './discover_grid_flyout'; +import { DiscoverGridFlyout, DiscoverGridFlyoutProps } from './discover_grid_flyout'; import { esHits } from '../../__mocks__/es_hits'; import { createFilterManagerMock } from '../../../../data/public/query/filter_manager/filter_manager.mock'; import { indexPatternMock } from '../../__mocks__/index_pattern'; @@ -17,34 +17,54 @@ import { DiscoverServices } from '../../build_services'; import { DocViewsRegistry } from '../../services/doc_views/doc_views_registry'; import { setDocViewsRegistry } from '../../kibana_services'; import { indexPatternWithTimefieldMock } from '../../__mocks__/index_pattern_with_timefield'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; +import { IndexPattern } from '../../../../data/common'; +import { ElasticSearchHit } from '../../types'; describe('Discover flyout', function () { setDocViewsRegistry(new DocViewsRegistry()); - const getProps = () => { + const mountComponent = ({ + indexPattern, + hits, + hitIndex, + }: { + indexPattern?: IndexPattern; + hits?: ElasticSearchHit[]; + hitIndex?: number; + }) => { const onClose = jest.fn(); const services = { filterManager: createFilterManagerMock(), addBasePath: (path: string) => `/base${path}`, + history: () => ({ location: {} }), } as unknown as DiscoverServices; - return { + const props = { columns: ['date'], - indexPattern: indexPatternMock, - hit: esHits[0], - hits: esHits, + indexPattern: indexPattern || indexPatternMock, + hit: hitIndex ? esHits[hitIndex] : esHits[0], + hits: hits || esHits, onAddColumn: jest.fn(), onClose, onFilter: jest.fn(), onRemoveColumn: jest.fn(), - services, setExpandedDoc: jest.fn(), }; + + const Proxy = (newProps: DiscoverGridFlyoutProps) => ( + + + + ); + + const component = mountWithIntl(); + + return { component, props }; }; it('should be rendered correctly using an index pattern without timefield', async () => { - const props = getProps(); - const component = mountWithIntl(); + const { component, props } = mountComponent({}); const url = findTestSubject(component, 'docTableRowAction').prop('href'); expect(url).toMatchInlineSnapshot(`"/base/app/discover#/doc/the-index-pattern-id/i?id=1"`); @@ -53,9 +73,7 @@ describe('Discover flyout', function () { }); it('should be rendered correctly using an index pattern with timefield', async () => { - const props = getProps(); - props.indexPattern = indexPatternWithTimefieldMock; - const component = mountWithIntl(); + const { component, props } = mountComponent({ indexPattern: indexPatternWithTimefieldMock }); const actions = findTestSubject(component, 'docTableRowAction'); expect(actions.length).toBe(2); @@ -70,24 +88,20 @@ describe('Discover flyout', function () { }); it('displays document navigation when there is more than 1 doc available', async () => { - const props = getProps(); - const component = mountWithIntl(); + const { component } = mountComponent({ indexPattern: indexPatternWithTimefieldMock }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeTruthy(); }); it('displays no document navigation when there are 0 docs available', async () => { - const props = getProps(); - props.hits = []; - const component = mountWithIntl(); + const { component } = mountComponent({ hits: [] }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeFalsy(); }); it('displays no document navigation when the expanded doc is not part of the given docs', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const props = getProps(); - props.hits = [ + const hits = [ { _index: 'new', _id: '1', @@ -103,15 +117,14 @@ describe('Discover flyout', function () { _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, }, ]; - const component = mountWithIntl(); + const { component } = mountComponent({ hits }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeFalsy(); }); it('allows you to navigate to the next doc, if expanded doc is the first', async () => { // scenario: you've expanded a doc, and in the next request different docs where fetched - const props = getProps(); - const component = mountWithIntl(); + const { component, props } = mountComponent({}); findTestSubject(component, 'pagination-button-next').simulate('click'); // we selected 1, so we'd expect 2 expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('2'); @@ -119,34 +132,28 @@ describe('Discover flyout', function () { it('doesnt allow you to navigate to the previous doc, if expanded doc is the first', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const props = getProps(); - const component = mountWithIntl(); + const { component, props } = mountComponent({}); findTestSubject(component, 'pagination-button-previous').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); }); it('doesnt allow you to navigate to the next doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const props = getProps(); - props.hit = props.hits[props.hits.length - 1]; - const component = mountWithIntl(); + const { component, props } = mountComponent({ hitIndex: esHits.length - 1 }); findTestSubject(component, 'pagination-button-next').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); }); it('allows you to navigate to the previous doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const props = getProps(); - props.hit = props.hits[props.hits.length - 1]; - const component = mountWithIntl(); + const { component, props } = mountComponent({ hitIndex: esHits.length - 1 }); findTestSubject(component, 'pagination-button-previous').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(1); expect(props.setExpandedDoc.mock.calls[0][0]._id).toBe('4'); }); it('allows navigating with arrow keys through documents', () => { - const props = getProps(); - const component = mountWithIntl(); + const { component, props } = mountComponent({}); findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowRight' }); expect(props.setExpandedDoc).toHaveBeenCalledWith(expect.objectContaining({ _id: '2' })); component.setProps({ ...props, hit: props.hits[1] }); @@ -155,8 +162,7 @@ describe('Discover flyout', function () { }); it('should not navigate with keypresses when already at the border of documents', () => { - const props = getProps(); - const component = mountWithIntl(); + const { component, props } = mountComponent({}); findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowLeft' }); expect(props.setExpandedDoc).not.toHaveBeenCalled(); component.setProps({ ...props, hit: props.hits[props.hits.length - 1] }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx index 726c211fe418b..371eb014eab8f 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx @@ -26,11 +26,11 @@ import { } from '@elastic/eui'; import { DocViewer } from '../../services/doc_views/components/doc_viewer/doc_viewer'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; -import { DiscoverServices } from '../../build_services'; import { useNavigationProps } from '../../utils/use_navigation_props'; import { ElasticSearchHit } from '../../types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; -interface Props { +export interface DiscoverGridFlyoutProps { columns: string[]; hit: ElasticSearchHit; hits?: ElasticSearchHit[]; @@ -39,7 +39,6 @@ interface Props { onClose: () => void; onFilter: DocViewFilterFn; onRemoveColumn: (column: string) => void; - services: DiscoverServices; setExpandedDoc: (doc: ElasticSearchHit) => void; } @@ -67,9 +66,9 @@ export function DiscoverGridFlyout({ onClose, onRemoveColumn, onAddColumn, - services, setExpandedDoc, -}: Props) { +}: DiscoverGridFlyoutProps) { + const services = useDiscoverServices(); // Get actual hit with updated highlighted searches const actualHit = useMemo(() => hits?.find(({ _id }) => _id === hit?._id) || hit, [hit, hits]); const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx index 07ed170258fb1..d3af215059af0 100644 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.test.tsx @@ -7,29 +7,27 @@ */ import React from 'react'; -import { ReactWrapper, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import { getRenderCellValueFn } from './get_render_cell_value'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { flattenHit } from 'src/plugins/data/common'; import { ElasticSearchHit } from '../../types'; -jest.mock('../../../../kibana_react/public', () => ({ - useUiSetting: () => true, - withKibana: (comp: ReactWrapper) => { - return comp; - }, -})); - -jest.mock('../../kibana_services', () => ({ - getServices: () => ({ +jest.mock('../../utils/use_discover_services', () => { + const services = { uiSettings: { - get: jest.fn((key) => key === 'discover:maxDocFieldsDisplayed' && 200), + get: (key: string) => key === 'discover:maxDocFieldsDisplayed' && 200, }, fieldFormats: { getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => (value ? value : '-') })), }, - }), -})); + }; + const originalModule = jest.requireActual('../../utils/use_discover_services'); + return { + ...originalModule, + useDiscoverServices: () => services, + }; +}); const rowsSource: ElasticSearchHit[] = [ { diff --git a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx index 5e1a1a7e39db8..fe2607415ace1 100644 --- a/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/components/discover_grid/get_render_cell_value.tsx @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import React, { Fragment, useContext, useEffect } from 'react'; +import React, { Fragment, useContext, useEffect, useMemo } from 'react'; import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme'; + import type { DataView, DataViewField } from 'src/plugins/data/common'; import { EuiDataGridCellValueElementProps, @@ -15,6 +16,7 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, } from '@elastic/eui'; +import { FieldFormatsStart } from '../../../../field_formats/public'; import { DiscoverGridContext } from './discover_grid_context'; import { JsonCodeEditor } from '../json_code_editor/json_code_editor'; import { defaultMonacoEditorWidth } from './constants'; @@ -22,10 +24,12 @@ import { EsHitRecord } from '../../application/types'; import { formatFieldValue } from '../../utils/format_value'; import { formatHit } from '../../utils/format_hit'; import { ElasticSearchHit } from '../../types'; +import { useDiscoverServices } from '../../utils/use_discover_services'; +import { MAX_DOC_FIELDS_DISPLAYED } from '../../../common'; export const getRenderCellValueFn = ( - indexPattern: DataView, + dataView: DataView, rows: ElasticSearchHit[] | undefined, rowsFlattened: Array>, useNewFieldsApi: boolean, @@ -33,12 +37,16 @@ export const getRenderCellValueFn = maxDocFieldsDisplayed: number ) => ({ rowIndex, columnId, isDetails, setCellProps }: EuiDataGridCellValueElementProps) => { + const { uiSettings, fieldFormats } = useDiscoverServices(); + + const maxEntries = useMemo(() => uiSettings.get(MAX_DOC_FIELDS_DISPLAYED), [uiSettings]); + const row = rows ? rows[rowIndex] : undefined; const rowFlattened = rowsFlattened ? (rowsFlattened[rowIndex] as Record) : undefined; - const field = indexPattern.fields.getByName(columnId); + const field = dataView.fields.getByName(columnId); const ctx = useContext(DiscoverGridContext); useEffect(() => { @@ -75,23 +83,24 @@ export const getRenderCellValueFn = ); if (isDetails) { - return renderPopoverContent( - row, + return renderPopoverContent({ + rowRaw: row, rowFlattened, field, columnId, - indexPattern, - useTopLevelObjectColumns - ); + dataView, + useTopLevelObjectColumns, + fieldFormats, + }); } if (field?.type === '_source' || useTopLevelObjectColumns) { const pairs = useTopLevelObjectColumns - ? getTopLevelObjectPairs(row, columnId, indexPattern, fieldsToShow).slice( + ? getTopLevelObjectPairs(row, columnId, dataView, fieldsToShow).slice( 0, maxDocFieldsDisplayed ) - : formatHit(row, indexPattern, fieldsToShow); + : formatHit(row, dataView, fieldsToShow, maxEntries, fieldFormats); return ( @@ -113,7 +122,7 @@ export const getRenderCellValueFn = // formatFieldValue guarantees sanitized values // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML={{ - __html: formatFieldValue(rowFlattened[columnId], row, indexPattern, field), + __html: formatFieldValue(rowFlattened[columnId], row, fieldFormats, dataView, field), }} /> ); @@ -134,14 +143,23 @@ function getInnerColumns(fields: Record, columnId: string) { /** * Helper function for the cell popover */ -function renderPopoverContent( - rowRaw: ElasticSearchHit, - rowFlattened: Record, - field: DataViewField | undefined, - columnId: string, - dataView: DataView, - useTopLevelObjectColumns: boolean -) { +function renderPopoverContent({ + rowRaw, + rowFlattened, + field, + columnId, + dataView, + useTopLevelObjectColumns, + fieldFormats, +}: { + rowRaw: ElasticSearchHit; + rowFlattened: Record; + field: DataViewField | undefined; + columnId: string; + dataView: DataView; + useTopLevelObjectColumns: boolean; + fieldFormats: FieldFormatsStart; +}) { if (useTopLevelObjectColumns || field?.type === '_source') { const json = useTopLevelObjectColumns ? getInnerColumns(rowRaw.fields as Record, columnId) @@ -156,7 +174,7 @@ function renderPopoverContent( // formatFieldValue guarantees sanitized values // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML={{ - __html: formatFieldValue(rowFlattened[columnId], rowRaw, dataView, field), + __html: formatFieldValue(rowFlattened[columnId], rowRaw, fieldFormats, dataView, field), }} /> ); diff --git a/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx index bdb57e3f1b586..60e9c25cb4532 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx @@ -12,6 +12,19 @@ import type { DataView, DataViewField } from 'src/plugins/data/common'; import { TableHeader } from './table_header'; import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; +import { DOC_HIDE_TIME_COLUMN_SETTING } from '../../../../../common'; +import { FORMATS_UI_SETTINGS } from '../../../../../../field_formats/common'; + +const defaultUiSettings = { + get: (key: string) => { + if (key === DOC_HIDE_TIME_COLUMN_SETTING) { + return false; + } else if (key === FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE) { + return false; + } + }, +}; function getMockIndexPattern() { return { @@ -66,11 +79,13 @@ describe('TableHeader with time column', () => { const props = getMockProps(); const wrapper = mountWithIntl( - - - - -
+ + + + + +
+
); test('renders correctly', () => { @@ -140,11 +155,24 @@ describe('TableHeader without time column', () => { const props = getMockProps({ hideTimeColumn: true }); const wrapper = mountWithIntl( - - - - -
+ { + if (key === DOC_HIDE_TIME_COLUMN_SETTING) { + return true; + } + }, + }, + }} + > + + + + +
+
); test('renders correctly', () => { diff --git a/src/plugins/discover/public/components/doc_table/components/table_header/table_header.tsx b/src/plugins/discover/public/components/doc_table/components/table_header/table_header.tsx index d78c17f9aca46..ffc0517ed5dd7 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_header/table_header.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_header/table_header.tsx @@ -6,18 +6,18 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { DataView } from 'src/plugins/data/common'; import { TableHeaderColumn } from './table_header_column'; import { SortOrder, getDisplayedColumns } from './helpers'; import { getDefaultSort } from '../../lib/get_default_sort'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; +import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../../../common'; +import { FORMATS_UI_SETTINGS } from '../../../../../../field_formats/common'; interface Props { columns: string[]; - defaultSortOrder: string; - hideTimeColumn: boolean; indexPattern: DataView; - isShortDots: boolean; onChangeSortOrder?: (sortOrder: SortOrder[]) => void; onMoveColumn?: (name: string, index: number) => void; onRemoveColumn?: (name: string) => void; @@ -26,15 +26,21 @@ interface Props { export function TableHeader({ columns, - defaultSortOrder, - hideTimeColumn, indexPattern, - isShortDots, onChangeSortOrder, onMoveColumn, onRemoveColumn, sortOrder, }: Props) { + const { uiSettings } = useDiscoverServices(); + const [defaultSortOrder, hideTimeColumn, isShortDots] = useMemo( + () => [ + uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), + uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + uiSettings.get(FORMATS_UI_SETTINGS.SHORT_DOTS_ENABLE), + ], + [uiSettings] + ); const displayedColumns = getDisplayedColumns(columns, indexPattern, hideTimeColumn, isShortDots); return ( diff --git a/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx b/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx index 7b0e4d821af65..084fddc991f74 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx @@ -9,28 +9,50 @@ import React from 'react'; import { mountWithIntl, findTestSubject } from '@kbn/test/jest'; import { TableRow, TableRowProps } from './table_row'; -import { setDocViewsRegistry, setServices } from '../../../kibana_services'; +import { setDocViewsRegistry } 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 '../../../services/doc_views/doc_views_registry'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; +import { discoverServiceMock } from '../../../__mocks__/services'; + +import { + DOC_HIDE_TIME_COLUMN_SETTING, + MAX_DOC_FIELDS_DISPLAYED, +} from '../../../../../discover/common'; jest.mock('../lib/row_formatter', () => { const originalModule = jest.requireActual('../lib/row_formatter'); return { ...originalModule, - formatRow: () => mocked_document_cell, + formatRow: () => { + return mocked_document_cell; + }, }; }); const mountComponent = (props: TableRowProps) => { return mountWithIntl( - - - - -
+ { + if (key === DOC_HIDE_TIME_COLUMN_SETTING) { + return true; + } else if (key === MAX_DOC_FIELDS_DISPLAYED) { + return 100; + } + }, + }, + }} + > + + + + +
+
); }; @@ -50,27 +72,18 @@ const mockHit = { const mockFilterManager = createFilterManagerMock(); describe('Doc table row component', () => { - let mockInlineFilter; - let defaultProps: TableRowProps; + const mockInlineFilter = jest.fn(); + const defaultProps = { + columns: ['_source'], + filter: mockInlineFilter, + indexPattern: indexPatternWithTimefieldMock, + row: mockHit, + useNewFieldsApi: true, + filterManager: mockFilterManager, + addBasePath: (path: string) => path, + } as unknown as 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()); }); diff --git a/src/plugins/discover/public/components/doc_table/components/table_row.tsx b/src/plugins/discover/public/components/doc_table/components/table_row.tsx index 8494c03f36fce..fde6edfb69cdf 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row.tsx @@ -13,13 +13,14 @@ import { EuiButtonEmpty, EuiIcon } from '@elastic/eui'; import { formatFieldValue } from '../../../utils/format_value'; import { flattenHit, DataView } from '../../../../../data/common'; import { DocViewer } from '../../../services/doc_views/components/doc_viewer/doc_viewer'; -import { FilterManager } from '../../../../../data/public'; import { TableCell } from './table_row/table_cell'; import { formatRow, formatTopLevelObject } from '../lib/row_formatter'; import { useNavigationProps } from '../../../utils/use_navigation_props'; import { DocViewFilterFn } from '../../../services/doc_views/doc_views_types'; import { ElasticSearchHit } from '../../../types'; import { TableRowDetails } from './table_row_details'; +import { useDiscoverServices } from '../../../utils/use_discover_services'; +import { DOC_HIDE_TIME_COLUMN_SETTING, MAX_DOC_FIELDS_DISPLAYED } from '../../../../common'; export type DocTableRow = ElasticSearchHit & { isAnchor?: boolean; @@ -28,15 +29,12 @@ export type DocTableRow = ElasticSearchHit & { export interface TableRowProps { columns: string[]; filter: DocViewFilterFn; - indexPattern: DataView; row: DocTableRow; - onAddColumn?: (column: string) => void; - onRemoveColumn?: (column: string) => void; + indexPattern: DataView; useNewFieldsApi: boolean; - hideTimeColumn: boolean; - filterManager: FilterManager; - addBasePath: (path: string) => string; fieldsToShow: string[]; + onAddColumn?: (column: string) => void; + onRemoveColumn?: (column: string) => void; } export const TableRow = ({ @@ -46,12 +44,17 @@ export const TableRow = ({ indexPattern, useNewFieldsApi, fieldsToShow, - hideTimeColumn, onAddColumn, onRemoveColumn, - filterManager, - addBasePath, }: TableRowProps) => { + const { uiSettings, filterManager, fieldFormats, addBasePath } = useDiscoverServices(); + const [maxEntries, hideTimeColumn] = useMemo( + () => [ + uiSettings.get(MAX_DOC_FIELDS_DISPLAYED), + uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + ], + [uiSettings] + ); const [open, setOpen] = useState(false); const docTableRowClassName = classNames('kbnDocTable__row', { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -75,12 +78,13 @@ export const TableRow = ({ // If we're formatting the _source column, don't use the regular field formatter, // but our Discover mechanism to format a hit in a better human-readable way. if (fieldName === '_source') { - return formatRow(row, indexPattern, fieldsToShow); + return formatRow(row, indexPattern, fieldsToShow, maxEntries, fieldFormats); } const formattedField = formatFieldValue( flattenedRow[fieldName], row, + fieldFormats, indexPattern, mapping(fieldName) ); @@ -142,7 +146,7 @@ export const TableRow = ({ } if (columns.length === 0 && useNewFieldsApi) { - const formatted = formatRow(row, indexPattern, fieldsToShow); + const formatted = formatRow(row, indexPattern, fieldsToShow, maxEntries, fieldFormats); rowCells.push( { + const services = useDiscoverServices(); const tableWrapperRef = useRef(null); const { curPageIndex, @@ -72,8 +73,8 @@ export const DocTableEmbeddable = (props: DocTableEmbeddableProps) => { ); const sampleSize = useMemo(() => { - return getServices().uiSettings.get(SAMPLE_SIZE_SETTING, 500); - }, []); + return services.uiSettings.get(SAMPLE_SIZE_SETTING, 500); + }, [services]); const renderDocTable = useCallback( (renderProps: DocTableRenderProps) => { diff --git a/src/plugins/discover/public/components/doc_table/doc_table_infinite.tsx b/src/plugins/discover/public/components/doc_table/doc_table_infinite.tsx index c3d86c646d407..8cda3b421815c 100644 --- a/src/plugins/discover/public/components/doc_table/doc_table_infinite.tsx +++ b/src/plugins/discover/public/components/doc_table/doc_table_infinite.tsx @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import React, { Fragment, memo, useCallback, useEffect, useRef, useState } from 'react'; +import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import './index.scss'; import { FormattedMessage } from '@kbn/i18n-react'; import { debounce } from 'lodash'; import { EuiButtonEmpty } from '@elastic/eui'; +import { SAMPLE_SIZE_SETTING } from '../../../common'; import { DocTableProps, DocTableRenderProps, DocTableWrapper } from './doc_table_wrapper'; import { SkipBottomButton } from '../../application/main/components/skip_bottom_button'; import { shouldLoadNextDocPatch } from './lib/should_load_next_doc_patch'; +import { useDiscoverServices } from '../../utils/use_discover_services'; const FOOTER_PADDING = { padding: 0 }; @@ -28,7 +30,6 @@ interface DocTableInfiniteContentProps extends DocTableRenderProps { const DocTableInfiniteContent = ({ rows, columnLength, - sampleSize, limit, onSkipBottomButtonClick, renderHeader, @@ -36,6 +37,10 @@ const DocTableInfiniteContent = ({ onSetMaxLimit, onBackToTop, }: DocTableInfiniteContentProps) => { + const { uiSettings } = useDiscoverServices(); + + const sampleSize = useMemo(() => uiSettings.get(SAMPLE_SIZE_SETTING, 500), [uiSettings]); + const onSkipBottomButton = useCallback(() => { onSetMaxLimit(); onSkipBottomButtonClick(); diff --git a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx index c6da2315a1757..3634a47e3bcf1 100644 --- a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx +++ b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx @@ -8,21 +8,15 @@ 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 { DocTableWrapper } from './doc_table_wrapper'; import { DocTableRow } from './components/table_row'; import { discoverServiceMock } from '../../__mocks__/services'; - -const mountComponent = (props: DocTableWrapperProps) => { - return mountWithIntl(); -}; +import { KibanaContextProvider } from '../../../../kibana_react/public'; describe('Doc table component', () => { - let defaultProps: DocTableWrapperProps; - - const initDefaults = (rows?: DocTableRow[]) => { - defaultProps = { + const mountComponent = (rows?: DocTableRow[]) => { + const props = { columns: ['_source'], indexPattern: indexPatternMock, rows: rows || [ @@ -53,21 +47,23 @@ describe('Doc table component', () => { }, }; - setServices(discoverServiceMock); + return mountWithIntl( + + + + ); }; it('should render infinite table correctly', () => { - initDefaults(); - const component = mountComponent(defaultProps); - expect(findTestSubject(component, defaultProps.dataTestSubj).exists()).toBeTruthy(); + const component = mountComponent(); + expect(findTestSubject(component, 'discoverDocTable').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(); + const component = mountComponent([]); + expect(findTestSubject(component, 'discoverDocTable').exists()).toBeTruthy(); expect(findTestSubject(component, 'docTable').exists()).toBeFalsy(); expect(component.find('.kbnDocTable__error').exists()).toBeTruthy(); }); diff --git a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx index 0cbfb36844943..cab38790efc4a 100644 --- a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx +++ b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx @@ -11,18 +11,12 @@ import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import type { DataView, DataViewField } from 'src/plugins/data/common'; 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, - SHOW_MULTIFIELDS, - SORT_DEFAULT_ORDER_SETTING, -} from '../../../common'; -import { getServices } from '../../kibana_services'; +import { SHOW_MULTIFIELDS } from '../../../common'; import { SortOrder } from './components/table_header/helpers'; import { DocTableRow, TableRow } from './components/table_row'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; import { getFieldsToShow } from '../../utils/get_fields_to_show'; +import { useDiscoverServices } from '../../utils/use_discover_services'; export interface DocTableProps { /** @@ -86,7 +80,6 @@ export interface DocTableProps { export interface DocTableRenderProps { columnLength: number; rows: DocTableRow[]; - sampleSize: number; renderRows: (row: DocTableRow[]) => JSX.Element[]; renderHeader: () => JSX.Element; onSkipBottomButtonClick: () => void; @@ -122,26 +115,8 @@ export const DocTableWrapper = forwardRef( }: DocTableWrapperProps, ref ) => { - const [ - defaultSortOrder, - hideTimeColumn, - isShortDots, - sampleSize, - showMultiFields, - 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.uiSettings.get(SHOW_MULTIFIELDS, false), - services.filterManager, - services.addBasePath, - ]; - }, []); + const { uiSettings } = useDiscoverServices(); + const showMultiFields = useMemo(() => uiSettings.get(SHOW_MULTIFIELDS, false), [uiSettings]); const onSkipBottomButtonClick = useCallback(async () => { // delay scrolling to after the rows have been rendered @@ -169,27 +144,14 @@ export const DocTableWrapper = forwardRef( () => ( ), - [ - columns, - defaultSortOrder, - hideTimeColumn, - indexPattern, - isShortDots, - onMoveColumn, - onRemoveColumn, - onSort, - sort, - ] + [columns, indexPattern, onMoveColumn, onRemoveColumn, onSort, sort] ); const renderRows = useCallback( @@ -202,27 +164,12 @@ export const DocTableWrapper = forwardRef( indexPattern={indexPattern} row={current} useNewFieldsApi={useNewFieldsApi} - hideTimeColumn={hideTimeColumn} onAddColumn={onAddColumn} - onRemoveColumn={onRemoveColumn} - filterManager={filterManager} - addBasePath={addBasePath} fieldsToShow={fieldsToShow} /> )); }, - [ - columns, - onFilter, - indexPattern, - useNewFieldsApi, - hideTimeColumn, - onAddColumn, - onRemoveColumn, - filterManager, - addBasePath, - fieldsToShow, - ] + [columns, onFilter, indexPattern, useNewFieldsApi, onAddColumn, fieldsToShow] ); return ( @@ -239,7 +186,6 @@ export const DocTableWrapper = forwardRef( render({ columnLength: columns.length, rows, - sampleSize, onSkipBottomButtonClick, renderHeader, renderRows, diff --git a/src/plugins/discover/public/components/doc_table/lib/row_formatter.test.ts b/src/plugins/discover/public/components/doc_table/lib/row_formatter.test.ts index 039b8c4ada684..683713af12c8c 100644 --- a/src/plugins/discover/public/components/doc_table/lib/row_formatter.test.ts +++ b/src/plugins/discover/public/components/doc_table/lib/row_formatter.test.ts @@ -10,7 +10,6 @@ import ReactDOM from 'react-dom/server'; import { formatRow, formatTopLevelObject } from './row_formatter'; import { DataView } from '../../../../../data/common'; import { fieldFormatsMock } from '../../../../../field_formats/common/mocks'; -import { setServices } from '../../../kibana_services'; import { DiscoverServices } from '../../../build_services'; import { stubbedSavedObjectIndexPattern } from '../../../../../data/common/stubs'; @@ -27,6 +26,7 @@ describe('Row formatter', () => { also: 'with "quotes" or \'single quotes\'', }, }; + let services: DiscoverServices; const createIndexPattern = () => { const id = 'my-index'; @@ -49,19 +49,17 @@ describe('Row formatter', () => { const fieldsToShow = indexPattern.fields.getAll().map((fld) => fld.name); beforeEach(() => { - setServices({ - uiSettings: { - get: () => 100, - }, + services = { fieldFormats: { getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), }, - } as unknown as DiscoverServices); + } as unknown as DiscoverServices; }); it('formats document properly', () => { - expect(formatRow(hit, indexPattern, fieldsToShow)).toMatchInlineSnapshot(` + expect(formatRow(hit, indexPattern, fieldsToShow, 100, services.fieldFormats)) + .toMatchInlineSnapshot(` { }); it('limits number of rendered items', () => { - setServices({ + services = { uiSettings: { get: () => 1, }, @@ -104,8 +102,8 @@ describe('Row formatter', () => { getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), }, - } as unknown as DiscoverServices); - expect(formatRow(hit, indexPattern, [])).toMatchInlineSnapshot(` + } as unknown as DiscoverServices; + expect(formatRow(hit, indexPattern, [], 1, services.fieldFormats)).toMatchInlineSnapshot(` { }); it('formats document with highlighted fields first', () => { - expect(formatRow({ ...hit, highlight: { number: ['42'] } }, indexPattern, fieldsToShow)) - .toMatchInlineSnapshot(` + expect( + formatRow( + { ...hit, highlight: { number: ['42'] } }, + indexPattern, + fieldsToShow, + 100, + services.fieldFormats + ) + ).toMatchInlineSnapshot(` { 'object.value': [5, 10], getByName: jest.fn(), }, - indexPattern + indexPattern, + 100 ) ).toMatchInlineSnapshot(` { formatTopLevelObject( { fields: { 'a.zzz': [100], 'a.ccc': [50] } }, { 'a.zzz': [100], 'a.ccc': [50], getByName: jest.fn() }, - indexPattern + indexPattern, + 100 ) ); expect(formatted.indexOf('
a.ccc:
')).toBeLessThan(formatted.indexOf('
a.zzz:
')); @@ -249,7 +256,8 @@ describe('Row formatter', () => { 'object.keys': ['a', 'b'], getByName: jest.fn(), }, - indexPattern + indexPattern, + 100 ) ).toMatchInlineSnapshot(` { 'object.value': [5, 10], getByName: jest.fn(), }, - indexPattern + indexPattern, + 100 ) ).toMatchInlineSnapshot(` { export const formatRow = ( hit: estypes.SearchHit, indexPattern: DataView, - fieldsToShow: string[] + fieldsToShow: string[], + maxEntries: number, + fieldFormats: FieldFormatsStart ) => { - const pairs = formatHit(hit, indexPattern, fieldsToShow); + const pairs = formatHit(hit, indexPattern, fieldsToShow, maxEntries, fieldFormats); return ; }; @@ -52,7 +53,8 @@ export const formatTopLevelObject = ( row: Record, // eslint-disable-next-line @typescript-eslint/no-explicit-any fields: Record, - indexPattern: DataView + indexPattern: DataView, + maxEntries: number ) => { const highlights = row.highlight ?? {}; const highlightPairs: Array<[string, string]> = []; @@ -77,6 +79,5 @@ export const formatTopLevelObject = ( const pairs = highlights[key] ? highlightPairs : sourcePairs; pairs.push([displayKey ? displayKey : key, formatted]); }); - const maxEntries = getServices().uiSettings.get(MAX_DOC_FIELDS_DISPLAYED); return ; }; diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index b203b6452c5ef..5e0f06f143a0c 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -11,6 +11,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; import { isEqual } from 'lodash'; +import { I18nProvider } from '@kbn/i18n-react'; import { Container, Embeddable } from '../../../embeddable/public'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; import { SavedSearch } from '../services/saved_searches'; @@ -28,7 +29,6 @@ import { } from '../../../data/common'; import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component'; import { UiActionsStart } from '../../../ui_actions/public'; -import { getServices } from '../kibana_services'; import { DOC_HIDE_TIME_COLUMN_SETTING, DOC_TABLE_LEGACY, @@ -46,9 +46,9 @@ import { getDefaultSort } from '../components/doc_table'; import { SortOrder } from '../components/doc_table/components/table_header/helpers'; import { VIEW_MODE } from '../components/view_mode_toggle'; import { updateSearchSource } from './utils/update_search_source'; -import { FieldStatsTableSavedSearchEmbeddable } from '../application/main/components/field_stats_table'; +import { FieldStatisticsTable } from '../application/main/components/field_stats_table'; import { ElasticSearchHit } from '../types'; -import { KibanaThemeProvider } from '../../../kibana_react/public'; +import { KibanaContextProvider, KibanaThemeProvider } from '../../../kibana_react/public'; export type SearchProps = Partial & Partial & { @@ -56,6 +56,7 @@ export type SearchProps = Partial & description?: string; sharedItemTitle?: string; inspectorAdapters?: Adapters; + services: DiscoverServices; filter?: (field: DataViewField, value: string[], operator: string) => void; hits?: ElasticSearchHit[]; @@ -337,7 +338,7 @@ export class SavedSearchEmbeddable ? this.savedSearch.sort : getDefaultSort( this.searchProps?.indexPattern, - getServices().uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc') + this.services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc') ); searchProps.sort = this.input.sort || savedSearchSort; searchProps.sharedItemTitle = this.panelTitle; @@ -392,18 +393,21 @@ export class SavedSearchEmbeddable Array.isArray(searchProps.columns) ) { ReactDOM.render( - - - , + + + + + + + , domNode ); return; @@ -416,9 +420,13 @@ export class SavedSearchEmbeddable }; if (searchProps.services) { ReactDOM.render( - - - , + + + + + + + , domNode ); } diff --git a/src/plugins/discover/public/embeddable/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/saved_search_grid.tsx index f0423eac4f963..b4785b9911da1 100644 --- a/src/plugins/discover/public/embeddable/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_grid.tsx @@ -5,51 +5,34 @@ * 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 { I18nProvider } from '@kbn/i18n-react'; +import React, { useState, memo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { I18nProvider } from '@kbn/i18n-react'; import { DiscoverGrid, DiscoverGridProps } from '../components/discover_grid/discover_grid'; -import { getServices } from '../kibana_services'; import { TotalDocuments } from '../application/main/components/total_documents/total_documents'; import { ElasticSearchHit } from '../types'; -import { KibanaContextProvider } from '../../../kibana_react/public'; export interface DiscoverGridEmbeddableProps extends DiscoverGridProps { totalHitCount: number; } -export const DataGridMemoized = React.memo((props: DiscoverGridProps) => ( - -)); +export const DataGridMemoized = memo(DiscoverGrid); export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { - const services = getServices(); const [expandedDoc, setExpandedDoc] = useState(undefined); return ( - - - {props.totalHitCount !== 0 && ( - - - - )} - - + + {props.totalHitCount !== 0 && ( + + - - + )} + + + + ); } diff --git a/src/plugins/discover/public/embeddable/search_embeddable_factory.ts b/src/plugins/discover/public/embeddable/search_embeddable_factory.ts index 8fbedf3979663..be391282ea57d 100644 --- a/src/plugins/discover/public/embeddable/search_embeddable_factory.ts +++ b/src/plugins/discover/public/embeddable/search_embeddable_factory.ts @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; -import { getServices } from '../kibana_services'; import { EmbeddableFactoryDefinition, Container, @@ -25,6 +24,7 @@ import { getSavedSearchUrl, throwErrorOnSavedSearchUrlConflict, } from '../services/saved_searches'; +import { DiscoverServices } from '../build_services'; interface StartServices { executeTriggerActions: UiActionsStart['executeTriggerActions']; @@ -43,7 +43,10 @@ export class SearchEmbeddableFactory getIconForSavedObject: () => 'discoverApp', }; - constructor(private getStartServices: () => Promise) {} + constructor( + private getStartServices: () => Promise, + private getDiscoverServices: () => Promise + ) {} public canCreateNew() { return false; @@ -64,7 +67,7 @@ export class SearchEmbeddableFactory input: Partial & { id: string; timeRange: TimeRange }, parent?: Container ): Promise => { - const services = getServices(); + const services = await this.getDiscoverServices(); const filterManager = services.filterManager; const url = getSavedSearchUrl(savedObjectId); const editUrl = services.addBasePath(`/app/discover${url}`); @@ -88,9 +91,9 @@ export class SearchEmbeddableFactory editUrl, editPath: url, filterManager, - editable: getServices().capabilities.discover.save as boolean, + editable: services.capabilities.discover.save as boolean, indexPatterns: indexPattern ? [indexPattern] : [], - services: getServices(), + services, }, input, executeTriggerActions, diff --git a/src/plugins/discover/public/embeddable/view_saved_search_action.test.ts b/src/plugins/discover/public/embeddable/view_saved_search_action.test.ts index c18999473f26d..7306e56e09fa8 100644 --- a/src/plugins/discover/public/embeddable/view_saved_search_action.test.ts +++ b/src/plugins/discover/public/embeddable/view_saved_search_action.test.ts @@ -11,14 +11,11 @@ import { ContactCardEmbeddable } from 'src/plugins/embeddable/public/lib/test_sa import { ViewSavedSearchAction } from './view_saved_search_action'; import { SavedSearchEmbeddable } from './saved_search_embeddable'; import { createStartContractMock } from '../__mocks__/start_contract'; -import { uiSettingsServiceMock } from '../../../../core/public/mocks'; import { savedSearchMock } from '../__mocks__/saved_search'; import { discoverServiceMock } from '../__mocks__/services'; import { DataView } from 'src/plugins/data/common'; import { createFilterManagerMock } from 'src/plugins/data/public/query/filter_manager/filter_manager.mock'; import { ViewMode } from 'src/plugins/embeddable/public'; -import { setServices } from '../kibana_services'; -import type { DiscoverServices } from '../build_services'; const applicationMock = createStartContractMock(); const savedSearch = savedSearchMock; @@ -48,11 +45,6 @@ const embeddableConfig = { }; describe('view saved search action', () => { - beforeEach(() => { - setServices({ - uiSettings: uiSettingsServiceMock.createStartContract(), - } as unknown as DiscoverServices); - }); it('is compatible when embeddable is of type saved search, in view mode && appropriate permissions are set', async () => { const action = new ViewSavedSearchAction(applicationMock); const embeddable = new SavedSearchEmbeddable( diff --git a/src/plugins/discover/public/kibana_services.ts b/src/plugins/discover/public/kibana_services.ts index ffdfd82058693..f055d2b67ade1 100644 --- a/src/plugins/discover/public/kibana_services.ts +++ b/src/plugins/discover/public/kibana_services.ts @@ -10,24 +10,12 @@ import { once } from 'lodash'; import { createHashHistory } from 'history'; import type { ScopedHistory, AppMountParameters } from 'kibana/public'; import type { UiActionsStart } from 'src/plugins/ui_actions/public'; -import { DiscoverServices, HistoryLocationState } from './build_services'; +import { HistoryLocationState } from './build_services'; import { createGetterSetter } from '../../kibana_utils/public'; import { DocViewsRegistry } from './services/doc_views/doc_views_registry'; -let services: DiscoverServices | null = null; let uiActions: UiActionsStart; -export function getServices(): DiscoverServices { - if (!services) { - throw new Error('Discover services are not yet available'); - } - return services; -} - -export function setServices(newServices: DiscoverServices) { - services = newServices; -} - export const setUiActions = (pluginUiActions: UiActionsStart) => (uiActions = pluginUiActions); export const getUiActions = () => uiActions; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index e881253a7d6d2..43c03a59b5b25 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -37,13 +37,11 @@ import { DocViewsRegistry } from './services/doc_views/doc_views_registry'; import { setDocViewsRegistry, setUrlTracker, - setServices, setHeaderActionMenuMounter, setUiActions, setScopedHistory, getScopedHistory, syncHistoryLocations, - getServices, } from './kibana_services'; import { registerFeature } from './register_feature'; import { buildServices } from './build_services'; @@ -64,6 +62,7 @@ import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public import { FieldFormatsStart } from '../../field_formats/public'; import { injectTruncateStyles } from './utils/truncate_styles'; import { DOC_TABLE_LEGACY, TRUNCATE_MAX_HEIGHT } from '../common'; +import { useDiscoverServices } from './utils/use_discover_services'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -211,10 +210,7 @@ export class DiscoverPlugin private urlGenerator?: DiscoverStart['urlGenerator']; private locator?: DiscoverAppLocator; - setup( - core: CoreSetup, - plugins: DiscoverSetupPlugins - ): DiscoverSetup { + setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { const baseUrl = core.http.basePath.prepend('/app/discover'); if (plugins.share) { @@ -242,9 +238,12 @@ export class DiscoverPlugin }), order: 10, component: (props) => { - const Component = getServices().uiSettings.get(DOC_TABLE_LEGACY) + // eslint-disable-next-line react-hooks/rules-of-hooks + const services = useDiscoverServices(); + const DocView = services.uiSettings.get(DOC_TABLE_LEGACY) ? DocViewerLegacyTable : DocViewerTable; + return ( } > - + ); }, @@ -339,7 +338,6 @@ export class DiscoverPlugin defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, mount: async (params: AppMountParameters) => { - const [, depsStart] = await core.getStartServices(); setScopedHistory(params.history); setHeaderActionMenuMounter(params.setHeaderActionMenu); syncHistoryLocations(); @@ -349,14 +347,18 @@ export class DiscoverPlugin const unlistenParentHistory = params.history.listen(() => { window.dispatchEvent(new HashChangeEvent('hashchange')); }); + + const [coreStart, discoverStartPlugins] = await core.getStartServices(); + const services = buildServices(coreStart, discoverStartPlugins, this.initializerContext); + // make sure the index pattern list is up to date - await depsStart.data.indexPatterns.clearCache(); + await discoverStartPlugins.data.indexPatterns.clearCache(); const { renderApp } = await import('./application'); // FIXME: Temporarily hide overflow-y in Discover app when Field Stats table is shown // due to EUI bug https://github.com/elastic/eui/pull/5152 params.element.classList.add('dscAppWrapper'); - const unmount = renderApp(params.element); + const unmount = renderApp(params.element, services); return () => { unlistenParentHistory(); unmount(); @@ -413,10 +415,7 @@ export class DiscoverPlugin uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', viewSavedSearchAction); setUiActions(plugins.uiActions); - const services = buildServices(core, plugins, this.initializerContext); - setServices(services); - - injectTruncateStyles(services.uiSettings.get(TRUNCATE_MAX_HEIGHT)); + injectTruncateStyles(core.uiSettings.get(TRUNCATE_MAX_HEIGHT)); return { urlGenerator: this.urlGenerator, @@ -439,7 +438,12 @@ export class DiscoverPlugin }; }; - const factory = new SearchEmbeddableFactory(getStartServices); + const getDiscoverServices = async () => { + const [coreStart, discoverStartPlugins] = await core.getStartServices(); + return buildServices(coreStart, discoverStartPlugins, this.initializerContext); + }; + + const factory = new SearchEmbeddableFactory(getStartServices, getDiscoverServices); plugins.embeddable.registerEmbeddableFactory(factory.type, factory); } } diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer.test.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer.test.tsx index bedf1047bc4ec..2d946db20dd03 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer.test.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer.test.tsx @@ -17,11 +17,6 @@ jest.mock('../../../../kibana_services', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let registry: any[] = []; return { - getServices: () => ({ - uiSettings: { - get: jest.fn(), - }, - }), getDocViewsRegistry: () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any addDocView(view: any) { @@ -37,6 +32,16 @@ jest.mock('../../../../kibana_services', () => { }; }); +jest.mock('../../../../utils/use_discover_services', () => { + return { + useDiscoverServices: { + uiSettings: { + get: jest.fn(), + }, + }, + }; +}); + beforeEach(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (getDocViewsRegistry() as any).resetRegistry(); diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer_tab.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer_tab.tsx index f43e445737820..107db6f1a588e 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer_tab.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer/doc_viewer_tab.tsx @@ -11,8 +11,6 @@ import { isEqual } from 'lodash'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; import { DocViewRenderFn, DocViewRenderProps } from '../../doc_views_types'; -import { getServices } from '../../../../kibana_services'; -import { KibanaContextProvider } from '../../../../../../kibana_react/public'; interface Props { id: number; @@ -67,11 +65,7 @@ export class DocViewerTab extends React.Component { // doc view is provided by a react component if (Component) { - return ( - - - - ); + return ; } return ( diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/__snapshots__/source.test.tsx.snap b/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/__snapshots__/source.test.tsx.snap index b78463a44e977..41b7ee37413d9 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/__snapshots__/source.test.tsx.snap +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/__snapshots__/source.test.tsx.snap @@ -10,108 +10,6 @@ exports[`Source Viewer component renders error state 1`] = ` "getComputedFields": [Function], } } - intl={ - Object { - "defaultFormats": Object {}, - "defaultLocale": "en", - "formatDate": [Function], - "formatHTMLMessage": [Function], - "formatMessage": [Function], - "formatNumber": [Function], - "formatPlural": [Function], - "formatRelative": [Function], - "formatTime": [Function], - "formats": Object { - "date": Object { - "full": Object { - "day": "numeric", - "month": "long", - "weekday": "long", - "year": "numeric", - }, - "long": Object { - "day": "numeric", - "month": "long", - "year": "numeric", - }, - "medium": Object { - "day": "numeric", - "month": "short", - "year": "numeric", - }, - "short": Object { - "day": "numeric", - "month": "numeric", - "year": "2-digit", - }, - }, - "number": Object { - "currency": Object { - "style": "currency", - }, - "percent": Object { - "style": "percent", - }, - }, - "relative": Object { - "days": Object { - "units": "day", - }, - "hours": Object { - "units": "hour", - }, - "minutes": Object { - "units": "minute", - }, - "months": Object { - "units": "month", - }, - "seconds": Object { - "units": "second", - }, - "years": Object { - "units": "year", - }, - }, - "time": Object { - "full": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "long": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "medium": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - }, - "short": Object { - "hour": "numeric", - "minute": "numeric", - }, - }, - }, - "formatters": Object { - "getDateTimeFormat": [Function], - "getMessageFormat": [Function], - "getNumberFormat": [Function], - "getPluralFormat": [Function], - "getRelativeFormat": [Function], - }, - "locale": "en", - "messages": Object {}, - "now": [Function], - "onError": [Function], - "textComponent": Symbol(react.fragment), - "timeZone": null, - } - } width={123} >
({ - getServices: jest.fn(), -})); - -import { getServices } from '../../../../kibana_services'; +import { KibanaContextProvider } from '../../../../../../kibana_react/public'; const mockIndexPattern = { getComputedFields: () => [], @@ -28,8 +23,7 @@ const getMock = jest.fn(() => Promise.resolve(mockIndexPattern)); const mockIndexPatternService = { get: getMock, } as unknown as DataView; - -(getServices as jest.Mock).mockImplementation(() => ({ +const services = { uiSettings: { get: (key: string) => { if (key === 'discover:useNewFieldsApi') { @@ -40,21 +34,24 @@ const mockIndexPatternService = { data: { indexPatternService: mockIndexPatternService, }, -})); +}; + describe('Source Viewer component', () => { test('renders loading state', () => { jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [0, null, () => {}]); const comp = mountWithIntl( - + + + ); - expect(comp).toMatchSnapshot(); + expect(comp.children()).toMatchSnapshot(); const loadingIndicator = comp.find(EuiLoadingSpinner); expect(loadingIndicator).not.toBe(null); }); @@ -63,15 +60,17 @@ describe('Source Viewer component', () => { jest.spyOn(hooks, 'useEsDocSearch').mockImplementation(() => [3, null, () => {}]); const comp = mountWithIntl( - + + + ); - expect(comp).toMatchSnapshot(); + expect(comp.children()).toMatchSnapshot(); const errorPrompt = comp.find(EuiEmptyPrompt); expect(errorPrompt.length).toBe(1); const refreshButton = comp.find(EuiButton); @@ -102,15 +101,17 @@ describe('Source Viewer component', () => { return false; }); const comp = mountWithIntl( - + + + ); - expect(comp).toMatchSnapshot(); + expect(comp.children()).toMatchSnapshot(); const jsonCodeEditor = comp.find(JsonCodeEditorCommon); expect(jsonCodeEditor).not.toBe(null); }); diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.tsx index 6712d1491606f..9f1cbb7069712 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.tsx @@ -12,8 +12,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { monaco } from '@kbn/monaco'; import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { JSONCodeEditorCommonMemoized } from '../../../../components/json_code_editor/json_code_editor_common'; -import { getServices } from '../../../../kibana_services'; import { SEARCH_FIELDS_FROM_SOURCE } from '../../../../../common'; import { useEsDocSearch } from '../../../../utils/use_es_doc_search'; import { DataView } from '../../../../../../data_views/common'; @@ -36,7 +36,8 @@ export const DocViewerSource = ({ }: SourceViewerProps) => { const [editor, setEditor] = useState(); const [jsonValue, setJsonValue] = useState(''); - const useNewFieldsApi = !getServices().uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + const { uiSettings } = useDiscoverServices(); + const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); const [reqState, hit, requestData] = useEsDocSearch({ id, index, diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx index 76977034da1b4..cb85315e7dd42 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx @@ -12,15 +12,11 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewerLegacyTable } from './table'; import { DataView } from '../../../../../../../data/common'; import { DocViewRenderProps } from '../../../doc_views_types'; - -jest.mock('../../../../../kibana_services', () => ({ - getServices: jest.fn(), -})); - -import { getServices } from '../../../../../kibana_services'; import { ElasticSearchHit } from '../../../../../types'; +import { KibanaContextProvider } from '../../../../../../../kibana_react/public'; +import { DiscoverServices } from 'src/plugins/discover/public/build_services'; -(getServices as jest.Mock).mockImplementation(() => ({ +const services = { uiSettings: { get: (key: string) => { if (key === 'discover:showMultiFields') { @@ -32,7 +28,7 @@ import { ElasticSearchHit } from '../../../../../types'; getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })), }, -})); +}; const indexPattern = { fields: { @@ -77,8 +73,12 @@ indexPattern.fields.getByName = (name: string) => { return indexPattern.fields.getAll().find((field) => field.name === name); }; -const mountComponent = (props: DocViewRenderProps) => { - return mountWithIntl(); +const mountComponent = (props: DocViewRenderProps, overrides?: Partial) => { + return mountWithIntl( + + {' '} + + ); }; describe('DocViewTable at Discover', () => { @@ -424,14 +424,16 @@ describe('DocViewTable at Discover Doc with Fields API', () => { }); it('does not render multifield rows if showMultiFields flag is not set', () => { - (getServices as jest.Mock).mockImplementationOnce(() => ({ + const overridedServices = { uiSettings: { get: (key: string) => { - return key === 'discover:showMultiFields' && false; + if (key === 'discover:showMultiFields') { + return false; + } }, }, - })); - const component = mountComponent(props); + } as unknown as DiscoverServices; + const component = mountComponent(props, overridedServices); const categoryKeywordRow = findTestSubject(component, 'tableDocViewRow-category.keyword'); expect(categoryKeywordRow.length).toBe(0); diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.tsx index 517093635897e..e78ed2ccadd06 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.tsx @@ -9,9 +9,9 @@ import '../table.scss'; import React, { useCallback, useMemo } from 'react'; import { EuiInMemoryTable } from '@elastic/eui'; +import { useDiscoverServices } from '../../../../../utils/use_discover_services'; import { flattenHit } from '../../../../../../../data/common'; import { SHOW_MULTIFIELDS } from '../../../../../../common'; -import { getServices } from '../../../../../kibana_services'; import { DocViewRenderProps, FieldRecordLegacy } from '../../../doc_views_types'; import { ACTIONS_COLUMN, MAIN_COLUMNS } from './table_columns'; import { getFieldsToShow } from '../../../../../utils/get_fields_to_show'; @@ -27,7 +27,8 @@ export const DocViewerLegacyTable = ({ onAddColumn, onRemoveColumn, }: DocViewRenderProps) => { - const showMultiFields = getServices().uiSettings.get(SHOW_MULTIFIELDS); + const { fieldFormats, uiSettings } = useDiscoverServices(); + const showMultiFields = useMemo(() => uiSettings.get(SHOW_MULTIFIELDS), [uiSettings]); const mapping = useCallback((name: string) => dataView.fields.getByName(name), [dataView.fields]); const tableColumns = useMemo(() => { @@ -89,7 +90,13 @@ export const DocViewerLegacyTable = ({ scripted: Boolean(fieldMapping?.scripted), }, value: { - formattedValue: formatFieldValue(flattened[field], hit, dataView, fieldMapping), + formattedValue: formatFieldValue( + flattened[field], + hit, + fieldFormats, + dataView, + fieldMapping + ), ignored, }, }; diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx index 65806bb03a0bd..521d7d6e75eb2 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/table.tsx @@ -27,12 +27,12 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { debounce } from 'lodash'; +import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { Storage } from '../../../../../../kibana_utils/public'; import { usePager } from '../../../../utils/use_pager'; import { FieldName } from '../../../../components/field_name/field_name'; import { flattenHit } from '../../../../../../data/common'; import { SHOW_MULTIFIELDS } from '../../../../../common'; -import { getServices } from '../../../../kibana_services'; import { DocViewRenderProps, FieldRecordLegacy } from '../../doc_views_types'; import { getFieldsToShow } from '../../../../utils/get_fields_to_show'; import { getIgnoredReason } from '../../../../utils/get_ignored_reason'; @@ -108,7 +108,7 @@ export const DocViewerTable = ({ onAddColumn, onRemoveColumn, }: DocViewRenderProps) => { - const { storage, uiSettings } = getServices(); + const { storage, uiSettings, fieldFormats } = useDiscoverServices(); const showMultiFields = uiSettings.get(SHOW_MULTIFIELDS); const currentDataViewId = dataView.id!; const isSingleDocView = !filter; @@ -178,21 +178,28 @@ export const DocViewerTable = ({ onTogglePinned, }, value: { - formattedValue: formatFieldValue(flattened[field], hit, dataView, fieldMapping), + formattedValue: formatFieldValue( + flattened[field], + hit, + fieldFormats, + dataView, + fieldMapping + ), ignored, }, }; }, [ - columns, - filter, - flattened, - hit, - dataView, mapping, + dataView, + hit, onToggleColumn, - onTogglePinned, + filter, + columns, + flattened, pinnedFields, + onTogglePinned, + fieldFormats, ] ); diff --git a/src/plugins/discover/public/services/doc_views/doc_views_types.ts b/src/plugins/discover/public/services/doc_views/doc_views_types.ts index e213adcb89553..0287884c8b6f6 100644 --- a/src/plugins/discover/public/services/doc_views/doc_views_types.ts +++ b/src/plugins/discover/public/services/doc_views/doc_views_types.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { ComponentType } from 'react'; import { DataView, DataViewField } from '../../../../data/common'; import { ElasticSearchHit } from '../../types'; import { IgnoredReason } from '../../utils/get_ignored_reason'; @@ -34,7 +33,7 @@ export interface DocViewRenderProps { onAddColumn?: (columnName: string) => void; onRemoveColumn?: (columnName: string) => void; } -export type DocViewerComponent = ComponentType; +export type DocViewerComponent = React.FC; export type DocViewRenderFn = ( domeNode: HTMLDivElement, renderProps: DocViewRenderProps diff --git a/src/plugins/discover/public/utils/format_hit.test.ts b/src/plugins/discover/public/utils/format_hit.test.ts index 8d37b87e884c4..601d88cdedc96 100644 --- a/src/plugins/discover/public/utils/format_hit.test.ts +++ b/src/plugins/discover/public/utils/format_hit.test.ts @@ -10,11 +10,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { indexPatternMock as dataViewMock } from '../__mocks__/index_pattern'; import { formatHit } from './format_hit'; import { discoverServiceMock } from '../__mocks__/services'; -import { MAX_DOC_FIELDS_DISPLAYED } from '../../common'; - -jest.mock('../kibana_services', () => ({ - getServices: () => jest.requireActual('../__mocks__/services').discoverServiceMock, -})); describe('formatHit', () => { let hit: estypes.SearchHit; @@ -32,9 +27,6 @@ describe('formatHit', () => { (dataViewMock.getFormatterForField as jest.Mock).mockReturnValue({ convert: (value: unknown) => `formatted:${value}`, }); - (discoverServiceMock.uiSettings.get as jest.Mock).mockImplementation( - (key) => key === MAX_DOC_FIELDS_DISPLAYED && 220 - ); }); afterEach(() => { @@ -42,7 +34,13 @@ describe('formatHit', () => { }); it('formats a document as expected', () => { - const formatted = formatHit(hit, dataViewMock, ['message', 'extension', 'object.value']); + const formatted = formatHit( + hit, + dataViewMock, + ['message', 'extension', 'object.value'], + 220, + discoverServiceMock.fieldFormats + ); expect(formatted).toEqual([ ['extension', 'formatted:png'], ['message', 'formatted:foobar'], @@ -53,11 +51,13 @@ describe('formatHit', () => { }); it('orders highlighted fields first', () => { - const formatted = formatHit({ ...hit, highlight: { message: ['%%'] } }, dataViewMock, [ - 'message', - 'extension', - 'object.value', - ]); + const formatted = formatHit( + { ...hit, highlight: { message: ['%%'] } }, + dataViewMock, + ['message', 'extension', 'object.value'], + 220, + discoverServiceMock.fieldFormats + ); expect(formatted.map(([fieldName]) => fieldName)).toEqual([ 'message', 'extension', @@ -68,10 +68,13 @@ describe('formatHit', () => { }); it('only limits count of pairs based on advanced setting', () => { - (discoverServiceMock.uiSettings.get as jest.Mock).mockImplementation( - (key) => key === MAX_DOC_FIELDS_DISPLAYED && 2 + const formatted = formatHit( + hit, + dataViewMock, + ['message', 'extension', 'object.value'], + 2, + discoverServiceMock.fieldFormats ); - const formatted = formatHit(hit, dataViewMock, ['message', 'extension', 'object.value']); expect(formatted).toEqual([ ['extension', 'formatted:png'], ['message', 'formatted:foobar'], @@ -80,7 +83,13 @@ describe('formatHit', () => { }); it('should not include fields not mentioned in fieldsToShow', () => { - const formatted = formatHit(hit, dataViewMock, ['message', 'object.value']); + const formatted = formatHit( + hit, + dataViewMock, + ['message', 'object.value'], + 220, + discoverServiceMock.fieldFormats + ); expect(formatted).toEqual([ ['message', 'formatted:foobar'], ['object.value', 'formatted:42,13'], @@ -90,7 +99,13 @@ describe('formatHit', () => { }); it('should filter fields based on their real name not displayName', () => { - const formatted = formatHit(hit, dataViewMock, ['bytes']); + const formatted = formatHit( + hit, + dataViewMock, + ['bytes'], + 220, + discoverServiceMock.fieldFormats + ); expect(formatted).toEqual([ ['bytesDisplayName', 'formatted:123'], ['_index', 'formatted:logs'], diff --git a/src/plugins/discover/public/utils/format_hit.ts b/src/plugins/discover/public/utils/format_hit.ts index 4a06162714a2a..4c94fd294ba3a 100644 --- a/src/plugins/discover/public/utils/format_hit.ts +++ b/src/plugins/discover/public/utils/format_hit.ts @@ -8,9 +8,8 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; +import { FieldFormatsStart } from '../../../field_formats/public'; import { DataView, flattenHit } from '../../../data/common'; -import { MAX_DOC_FIELDS_DISPLAYED } from '../../common'; -import { getServices } from '../kibana_services'; import { formatFieldValue } from './format_value'; const formattedHitCache = new WeakMap(); @@ -28,7 +27,9 @@ type FormattedHit = Array; export function formatHit( hit: estypes.SearchHit, dataView: DataView, - fieldsToShow: string[] + fieldsToShow: string[], + maxEntries: number, + fieldFormats: FieldFormatsStart ): FormattedHit { const cached = formattedHitCache.get(hit); if (cached) { @@ -50,7 +51,13 @@ export function formatHit( const displayKey = dataView.fields.getByName(key)?.displayName; const pairs = highlights[key] ? highlightPairs : sourcePairs; // Format the raw value using the regular field formatters for that field - const formattedValue = formatFieldValue(val, hit, dataView, dataView.fields.getByName(key)); + const formattedValue = formatFieldValue( + val, + hit, + fieldFormats, + dataView, + dataView.fields.getByName(key) + ); // If the field was a mapped field, we validate it against the fieldsToShow list, if not // we always include it into the result. if (displayKey) { @@ -61,7 +68,6 @@ export function formatHit( pairs.push([key, formattedValue]); } }); - const maxEntries = getServices().uiSettings.get(MAX_DOC_FIELDS_DISPLAYED); const pairs = [...highlightPairs, ...sourcePairs]; const formatted = // If document has more formatted fields than configured via MAX_DOC_FIELDS_DISPLAYED we cut diff --git a/src/plugins/discover/public/utils/format_value.test.ts b/src/plugins/discover/public/utils/format_value.test.ts index 4684547b7cf3e..9fbf38c21b4b7 100644 --- a/src/plugins/discover/public/utils/format_value.test.ts +++ b/src/plugins/discover/public/utils/format_value.test.ts @@ -6,22 +6,18 @@ * Side Public License, v 1. */ +import { FieldFormatsStart } from '../../../field_formats/public'; import type { FieldFormat } from '../../../field_formats/common'; import { indexPatternMock } from '../__mocks__/index_pattern'; import { formatFieldValue } from './format_value'; -import { getServices } from '../kibana_services'; - -jest.mock('../kibana_services', () => { - const services = { - fieldFormats: { - getDefaultInstance: jest.fn( - () => ({ convert: (value: unknown) => value } as FieldFormat) - ), - }, - }; - return { getServices: () => services }; -}); +const services = { + fieldFormats: { + getDefaultInstance: jest.fn( + () => ({ convert: (value: unknown) => value } as FieldFormat) + ), + } as unknown as FieldFormatsStart, +}; const hit = { _id: '1', @@ -34,7 +30,6 @@ const hit = { describe('formatFieldValue', () => { afterEach(() => { (indexPatternMock.getFormatterForField as jest.Mock).mockReset(); - (getServices().fieldFormats.getDefaultInstance as jest.Mock).mockReset(); }); it('should call correct fieldFormatter for field', () => { @@ -42,28 +37,32 @@ describe('formatFieldValue', () => { const convertMock = jest.fn((value: unknown) => `formatted:${value}`); formatterForFieldMock.mockReturnValue({ convert: convertMock }); const field = indexPatternMock.fields.getByName('message'); - expect(formatFieldValue('foo', hit, indexPatternMock, field)).toBe('formatted:foo'); + expect(formatFieldValue('foo', hit, services.fieldFormats, indexPatternMock, field)).toBe( + 'formatted:foo' + ); expect(indexPatternMock.getFormatterForField).toHaveBeenCalledWith(field); expect(convertMock).toHaveBeenCalledWith('foo', 'html', { field, hit }); }); it('should call default string formatter if no field specified', () => { const convertMock = jest.fn((value: unknown) => `formatted:${value}`); - (getServices().fieldFormats.getDefaultInstance as jest.Mock).mockReturnValue({ + (services.fieldFormats.getDefaultInstance as jest.Mock).mockReturnValue({ convert: convertMock, }); - expect(formatFieldValue('foo', hit, indexPatternMock)).toBe('formatted:foo'); - expect(getServices().fieldFormats.getDefaultInstance).toHaveBeenCalledWith('string'); + expect(formatFieldValue('foo', hit, services.fieldFormats, indexPatternMock)).toBe( + 'formatted:foo' + ); + expect(services.fieldFormats.getDefaultInstance).toHaveBeenCalledWith('string'); expect(convertMock).toHaveBeenCalledWith('foo', 'html', { field: undefined, hit }); }); it('should call default string formatter if no indexPattern is specified', () => { const convertMock = jest.fn((value: unknown) => `formatted:${value}`); - (getServices().fieldFormats.getDefaultInstance as jest.Mock).mockReturnValue({ + (services.fieldFormats.getDefaultInstance as jest.Mock).mockReturnValue({ convert: convertMock, }); - expect(formatFieldValue('foo', hit)).toBe('formatted:foo'); - expect(getServices().fieldFormats.getDefaultInstance).toHaveBeenCalledWith('string'); + expect(formatFieldValue('foo', hit, services.fieldFormats)).toBe('formatted:foo'); + expect(services.fieldFormats.getDefaultInstance).toHaveBeenCalledWith('string'); expect(convertMock).toHaveBeenCalledWith('foo', 'html', { field: undefined, hit }); }); }); diff --git a/src/plugins/discover/public/utils/format_value.ts b/src/plugins/discover/public/utils/format_value.ts index 7a2a67b063191..432978f7fb41f 100644 --- a/src/plugins/discover/public/utils/format_value.ts +++ b/src/plugins/discover/public/utils/format_value.ts @@ -7,8 +7,8 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { FieldFormatsStart } from '../../../field_formats/public'; import { DataView, DataViewField, KBN_FIELD_TYPES } from '../../../data/common'; -import { getServices } from '../kibana_services'; /** * Formats the value of a specific field using the appropriate field formatter if available @@ -23,14 +23,15 @@ import { getServices } from '../kibana_services'; export function formatFieldValue( value: unknown, hit: estypes.SearchHit, + fieldFormats: FieldFormatsStart, dataView?: DataView, field?: DataViewField ): string { if (!dataView || !field) { // If either no field is available or no data view, we'll use the default // string formatter to format that field. - return getServices() - .fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING) + return fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.STRING) .convert(value, 'html', { hit, field }); } diff --git a/src/plugins/discover/public/utils/use_discover_services.ts b/src/plugins/discover/public/utils/use_discover_services.ts new file mode 100644 index 0000000000000..d9a6a2d77e481 --- /dev/null +++ b/src/plugins/discover/public/utils/use_discover_services.ts @@ -0,0 +1,12 @@ +/* + * 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 { useKibana } from '../../../kibana_react/public'; +import { DiscoverServices } from '../build_services'; + +export const useDiscoverServices = () => useKibana().services; diff --git a/src/plugins/discover/public/utils/use_es_doc_search.test.tsx b/src/plugins/discover/public/utils/use_es_doc_search.test.tsx index 91115f70aebf2..629a2b4d10470 100644 --- a/src/plugins/discover/public/utils/use_es_doc_search.test.tsx +++ b/src/plugins/discover/public/utils/use_es_doc_search.test.tsx @@ -13,27 +13,27 @@ import { DataView } from 'src/plugins/data/common'; import { DocProps } from '../application/doc/components/doc'; import { ElasticRequestState } from '../application/doc/types'; import { SEARCH_FIELDS_FROM_SOURCE as mockSearchFieldsFromSource } from '../../common'; +import { KibanaContextProvider } from '../../../kibana_react/public'; +import React from 'react'; const mockSearchResult = new Observable(); -jest.mock('../kibana_services', () => ({ - getServices: () => ({ - data: { - search: { - search: jest.fn(() => { - return mockSearchResult; - }), - }, +const services = { + data: { + search: { + search: jest.fn(() => { + return mockSearchResult; + }), }, - uiSettings: { - get: (key: string) => { - if (key === mockSearchFieldsFromSource) { - return false; - } - }, + }, + uiSettings: { + get: (key: string) => { + if (key === mockSearchFieldsFromSource) { + return false; + } }, - }), -})); + }, +}; describe('Test of helper / hook', () => { test('buildSearchBody given useNewFieldsApi is false', () => { @@ -181,7 +181,12 @@ describe('Test of helper / hook', () => { indexPattern, } as unknown as DocProps; - const { result } = renderHook((p: DocProps) => useEsDocSearch(p), { initialProps: props }); + const { result } = renderHook((p: DocProps) => useEsDocSearch(p), { + initialProps: props, + wrapper: ({ children }) => ( + {children} + ), + }); expect(result.current.slice(0, 2)).toEqual([ElasticRequestState.Loading, null]); }); diff --git a/src/plugins/discover/public/utils/use_es_doc_search.ts b/src/plugins/discover/public/utils/use_es_doc_search.ts index 1c8fd2f77888e..ac94fccdd3e12 100644 --- a/src/plugins/discover/public/utils/use_es_doc_search.ts +++ b/src/plugins/discover/public/utils/use_es_doc_search.ts @@ -11,9 +11,9 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DataView } from '../../../data/common'; import { DocProps } from '../application/doc/components/doc'; import { ElasticRequestState } from '../application/doc/types'; -import { getServices } from '../kibana_services'; import { SEARCH_FIELDS_FROM_SOURCE } from '../../common'; import { ElasticSearchHit } from '../types'; +import { useDiscoverServices } from './use_discover_services'; type RequestBody = Pick; @@ -69,7 +69,7 @@ export function useEsDocSearch({ }: DocProps): [ElasticRequestState, ElasticSearchHit | null, () => void] { const [status, setStatus] = useState(ElasticRequestState.Loading); const [hit, setHit] = useState(null); - const { data, uiSettings } = useMemo(() => getServices(), []); + const { data, uiSettings } = useDiscoverServices(); const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); const requestData = useCallback(async () => { diff --git a/src/plugins/discover/public/utils/use_navigation_props.test.tsx b/src/plugins/discover/public/utils/use_navigation_props.test.tsx index 29d4976f265c3..de1f1dbcc9b01 100644 --- a/src/plugins/discover/public/utils/use_navigation_props.test.tsx +++ b/src/plugins/discover/public/utils/use_navigation_props.test.tsx @@ -17,8 +17,7 @@ import { } from './use_navigation_props'; import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; -import { setServices } from '../kibana_services'; -import { DiscoverServices } from '../build_services'; +import { KibanaContextProvider } from '../../../kibana_react/public'; const filterManager = createFilterManagerMock(); const defaultProps = { @@ -45,16 +44,17 @@ const getContextRoute = () => { return `/context/${defaultProps.indexPatternId}/${defaultProps.rowId}`; }; -const render = () => { +const render = (withRouter = true, props?: Partial) => { const history = createMemoryHistory({ initialEntries: ['/' + getSearch()], }); - setServices({ history: () => history } as unknown as DiscoverServices); const wrapper = ({ children }: { children: ReactElement }) => ( - {children} + history }}> + {withRouter ? {children} : children} + ); return { - result: renderHook(() => useNavigationProps(defaultProps), { wrapper }).result, + result: renderHook(() => useNavigationProps({ ...defaultProps, ...props }), { wrapper }).result, history, }; }; @@ -81,12 +81,7 @@ describe('useNavigationProps', () => { }); test('should create valid links to the context and single doc pages from embeddable', () => { - const { result } = renderHook(() => - useNavigationProps({ - ...defaultProps, - addBasePath: (val: string) => `${basePathPrefix}${val}`, - }) - ); + const { result } = render(false, { addBasePath: (val: string) => `${basePathPrefix}${val}` }); expect(result.current.singleDocProps.href!).toEqual( `${basePathPrefix}/app/discover#${getSingeDocRoute()}?id=${defaultProps.rowId}` diff --git a/src/plugins/discover/public/utils/use_navigation_props.tsx b/src/plugins/discover/public/utils/use_navigation_props.tsx index 6f1dedf75e730..963499c4cad57 100644 --- a/src/plugins/discover/public/utils/use_navigation_props.tsx +++ b/src/plugins/discover/public/utils/use_navigation_props.tsx @@ -8,11 +8,12 @@ import { useMemo, useRef } from 'react'; import { useHistory, matchPath } from 'react-router-dom'; +import type { Location } from 'history'; import { stringify } from 'query-string'; import rison from 'rison-node'; import { esFilters, FilterManager } from '../../../data/public'; import { url } from '../../../kibana_utils/common'; -import { getServices } from '../kibana_services'; +import { useDiscoverServices } from './use_discover_services'; export type DiscoverNavigationProps = { onClick: () => void } | { href: string }; @@ -53,12 +54,13 @@ export const getContextHash = (columns: string[], filterManager: FilterManager) * Current history object should be used in callback, since url state might be changed * after expanded document opened. */ -const getCurrentBreadcrumbs = (isContextRoute: boolean, prevBreadcrumb?: string) => { - const { history: getHistory } = getServices(); - const currentHistory = getHistory(); - return isContextRoute - ? prevBreadcrumb - : '#' + currentHistory?.location.pathname + currentHistory?.location.search; + +const getCurrentBreadcrumbs = ( + isContextRoute: boolean, + currentLocation: Location, + prevBreadcrumb?: string +) => { + return isContextRoute ? prevBreadcrumb : '#' + currentLocation.pathname + currentLocation.search; }; export const useMainRouteBreadcrumb = () => { @@ -75,6 +77,8 @@ export const useNavigationProps = ({ addBasePath, }: UseNavigationProps) => { const history = useHistory(); + const currentLocation = useDiscoverServices().history().location; + const prevBreadcrumb = useRef(history?.location.state?.breadcrumb).current; const contextSearchHash = useMemo( () => getContextHash(columns, filterManager), @@ -95,7 +99,9 @@ export const useNavigationProps = ({ history.push({ pathname: `/doc/${indexPatternId}/${rowIndex}`, search: `?id=${encodeURIComponent(rowId)}`, - state: { breadcrumb: getCurrentBreadcrumbs(!!isContextRoute, prevBreadcrumb) }, + state: { + breadcrumb: getCurrentBreadcrumbs(!!isContextRoute, currentLocation, prevBreadcrumb), + }, }); }; @@ -105,7 +111,9 @@ export const useNavigationProps = ({ String(rowId) )}`, search: `?${contextSearchHash}`, - state: { breadcrumb: getCurrentBreadcrumbs(!!isContextRoute, prevBreadcrumb) }, + state: { + breadcrumb: getCurrentBreadcrumbs(!!isContextRoute, currentLocation, prevBreadcrumb), + }, }); return { diff --git a/src/plugins/discover/public/utils/with_query_params.test.tsx b/src/plugins/discover/public/utils/with_query_params.test.tsx index d897c3327a06f..3d416d6a3e8b5 100644 --- a/src/plugins/discover/public/utils/with_query_params.test.tsx +++ b/src/plugins/discover/public/utils/with_query_params.test.tsx @@ -11,13 +11,6 @@ import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; import { mountWithIntl } from '@kbn/test/jest'; import { withQueryParams } from './with_query_params'; -import { DiscoverServices } from '../build_services'; -import { DiscoverRouteProps } from '../application/types'; - -interface ComponentProps extends DiscoverRouteProps { - id: string; - query: string; -} const mountComponent = (children: ReactElement, query = '') => { const history = createMemoryHistory({ @@ -29,27 +22,16 @@ const mountComponent = (children: ReactElement, query = '') => { describe('withQueryParams', () => { it('should display error message, when query does not contain required parameters', () => { const Component = withQueryParams(() =>
, ['id', 'query']); - const component = mountComponent(); + const component = mountComponent(); expect(component.html()).toContain('Cannot load this page'); expect(component.html()).toContain('URL query string is missing id, query.'); }); it('should not display error message, when query contain required parameters', () => { - const Component = withQueryParams( - ({ id, query }: ComponentProps) => ( -
- {id} and {query} are presented -
- ), - ['id', 'query'] - ); - const component = mountComponent( - , - '?id=one&query=another' - ); + const Component = withQueryParams(() =>
, ['id', 'query']); + const component = mountComponent(, '?id=one&query=another'); - expect(component.html()).toContain('one and another are presented'); expect(component.html()).not.toContain('URL query string is missing id, query.'); }); }); diff --git a/src/plugins/discover/public/utils/with_query_params.tsx b/src/plugins/discover/public/utils/with_query_params.tsx index 66f0dd72c64de..159ba6740cf64 100644 --- a/src/plugins/discover/public/utils/with_query_params.tsx +++ b/src/plugins/discover/public/utils/with_query_params.tsx @@ -9,20 +9,12 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { useLocation } from 'react-router-dom'; -import { DiscoverRouteProps } from '../application/types'; import { DiscoverError } from '../components/common/error_alert'; -function useQuery() { - const { search } = useLocation(); - return useMemo(() => new URLSearchParams(search), [search]); -} - -export const withQueryParams =

( - Component: React.ComponentType

, - requiredParams: string[] -) => { - return (routeProps: DiscoverRouteProps) => { - const query = useQuery(); +export function withQueryParams(Component: React.ComponentType, requiredParams: string[]) { + return () => { + const { search } = useLocation(); + const query = useMemo(() => new URLSearchParams(search), [search]); const missingParamNames = useMemo( () => requiredParams.filter((currentParamName) => !query.get(currentParamName)), @@ -42,6 +34,6 @@ export const withQueryParams =

( const queryProps = Object.fromEntries( requiredParams.map((current) => [[current], query.get(current)]) ); - return ; + return ; }; -}; +}