diff --git a/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts index aa130b5030fc7..d7fca9820e614 100644 --- a/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts @@ -6,12 +6,14 @@ jest.mock('ui/new_platform'); -export function XJsonMode() {} -export function setDependencyCache() {} -export const useRequest = () => ({ +export const expandLiteralStrings = jest.fn(); +export const XJsonMode = jest.fn(); +export const setDependencyCache = jest.fn(); +export const useRequest = jest.fn(() => ({ isLoading: false, error: null, data: undefined, -}); +})); export { mlInMemoryTableBasicFactory } from '../../../ml/public/application/components/ml_in_memory_table'; export const SORT_DIRECTION = { ASC: 'asc' }; +export const KqlFilterBar = jest.fn(() => null); diff --git a/x-pack/legacy/plugins/transform/public/app/common/request.ts b/x-pack/legacy/plugins/transform/public/app/common/request.ts index 3b740de177ef8..31089b86a2c2d 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/request.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/request.ts @@ -7,7 +7,7 @@ import { DefaultOperator } from 'elasticsearch'; import { dictionaryToArray } from '../../../common/types/common'; -import { SavedSearchQuery } from '../lib/kibana'; +import { SavedSearchQuery } from '../hooks/use_search_items'; import { StepDefineExposedState } from '../sections/create_transform/components/step_define/step_define_form'; import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form'; diff --git a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx index 81af5c974fe04..095b57de97d9a 100644 --- a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx @@ -7,13 +7,13 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { KibanaContext } from '../lib/kibana'; import { createPublicShim } from '../../shim'; import { getAppProviders } from '../app_dependencies'; import { ToastNotificationText } from './toast_notification_text'; jest.mock('../../shared_imports'); +jest.mock('ui/new_platform'); describe('ToastNotificationText', () => { test('should render the text as plain text', () => { @@ -23,9 +23,7 @@ describe('ToastNotificationText', () => { }; const { container } = render( - - - + ); expect(container.textContent).toBe('a short text message'); @@ -39,9 +37,7 @@ describe('ToastNotificationText', () => { }; const { container } = render( - - - + ); expect(container.textContent).toBe( diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/common.ts similarity index 96% rename from x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts rename to x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/common.ts index aa4cd21281e22..2258f8f33f01d 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/common.ts @@ -14,6 +14,8 @@ import { import { matchAllQuery } from '../../common'; +export type SavedSearchQuery = object; + type IndexPatternId = string; type SavedSearchId = string; @@ -60,7 +62,7 @@ export function getIndexPatternIdByTitle(indexPatternTitle: string): string | un return indexPatternCache.find(d => d?.attributes?.title === indexPatternTitle)?.id; } -type CombinedQuery = Record<'bool', any> | unknown; +type CombinedQuery = Record<'bool', any> | object; export function loadCurrentIndexPattern( indexPatterns: IndexPatternsContract, @@ -79,17 +81,20 @@ export function loadCurrentSavedSearch(savedSearches: any, savedSearchId: SavedS function isIndexPattern(arg: any): arg is IndexPattern { return arg !== undefined; } + +export interface SearchItems { + indexPattern: IndexPattern; + savedSearch: any; + query: any; + combinedQuery: CombinedQuery; +} + // Helper for creating the items used for searching and job creation. export function createSearchItems( indexPattern: IndexPattern | undefined, savedSearch: any, config: IUiSettingsClient -): { - indexPattern: IndexPattern; - savedSearch: any; - query: any; - combinedQuery: CombinedQuery; -} { +): SearchItems { // query is only used by the data visualizer as it needs // a lucene query_string. // Using a blank query will cause match_all:{} to be used diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/index.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/index.ts new file mode 100644 index 0000000000000..aa4f04f43b335 --- /dev/null +++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SavedSearchQuery, SearchItems } from './common'; +export { useSearchItems } from './use_search_items'; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts similarity index 53% rename from x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx rename to x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts index f2574a4a85f29..12fc75c20ffa4 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx +++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts @@ -4,30 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState, FC } from 'react'; +import { useEffect, useState } from 'react'; + +import { createSavedSearchesLoader } from '../../../shared_imports'; import { useAppDependencies } from '../../app_dependencies'; import { createSearchItems, + getIndexPatternIdByTitle, loadCurrentIndexPattern, loadIndexPatterns, loadCurrentSavedSearch, + SearchItems, } from './common'; -import { InitializedKibanaContextValue, KibanaContext, KibanaContextValue } from './kibana_context'; - -interface Props { - savedObjectId: string; -} +export const useSearchItems = (defaultSavedObjectId: string | undefined) => { + const [savedObjectId, setSavedObjectId] = useState(defaultSavedObjectId); -export const KibanaProvider: FC = ({ savedObjectId, children }) => { const appDeps = useAppDependencies(); const indexPatterns = appDeps.plugins.data.indexPatterns; + const uiSettings = appDeps.core.uiSettings; const savedObjectsClient = appDeps.core.savedObjects.client; - const savedSearches = appDeps.plugins.savedSearches.getClient(); + const savedSearches = createSavedSearchesLoader({ + savedObjectsClient, + indexPatterns, + chrome: appDeps.core.chrome, + overlays: appDeps.core.overlays, + }); - const [contextValue, setContextValue] = useState({ initialized: false }); + const [searchItems, setSearchItems] = useState(undefined); async function fetchSavedObject(id: string) { await loadIndexPatterns(savedObjectsClient, indexPatterns); @@ -47,31 +53,21 @@ export const KibanaProvider: FC = ({ savedObjectId, children }) => { // Just let fetchedSavedSearch stay undefined in case it doesn't exist. } - const kibanaConfig = appDeps.core.uiSettings; - - const { - indexPattern: currentIndexPattern, - savedSearch: currentSavedSearch, - combinedQuery, - } = createSearchItems(fetchedIndexPattern, fetchedSavedSearch, kibanaConfig); - - const kibanaContext: InitializedKibanaContextValue = { - indexPatterns, - initialized: true, - kibanaConfig, - combinedQuery, - currentIndexPattern, - currentSavedSearch, - }; - - setContextValue(kibanaContext); + setSearchItems(createSearchItems(fetchedIndexPattern, fetchedSavedSearch, uiSettings)); } useEffect(() => { - fetchSavedObject(savedObjectId); - // fetchSavedObject should not be tracked. + if (savedObjectId !== undefined) { + fetchSavedObject(savedObjectId); + } + // Run this only when savedObjectId changes. // eslint-disable-next-line react-hooks/exhaustive-deps }, [savedObjectId]); - return {children}; + return { + getIndexPatternIdByTitle, + loadIndexPatterns, + searchItems, + setSavedObjectId, + }; }; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts deleted file mode 100644 index 62107cb37ff2c..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getIndexPatternIdByTitle, loadIndexPatterns } from './common'; -export { - useKibanaContext, - InitializedKibanaContextValue, - KibanaContext, - KibanaContextValue, - SavedSearchQuery, - RenderOnlyWithInitializedKibanaContext, -} from './kibana_context'; -export { KibanaProvider } from './kibana_provider'; -export { useCurrentIndexPattern } from './use_current_index_pattern'; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx deleted file mode 100644 index 7677c491a7a59..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { createContext, useContext, FC } from 'react'; - -import { IUiSettingsClient } from 'kibana/public'; - -import { - IndexPattern, - IndexPatternsContract, -} from '../../../../../../../../src/plugins/data/public'; -import { SavedSearch } from '../../../../../../../../src/plugins/discover/public/'; - -interface UninitializedKibanaContextValue { - initialized: false; -} - -export interface InitializedKibanaContextValue { - combinedQuery: any; - indexPatterns: IndexPatternsContract; - initialized: true; - kibanaConfig: IUiSettingsClient; - currentIndexPattern: IndexPattern; - currentSavedSearch?: SavedSearch; -} - -export type KibanaContextValue = UninitializedKibanaContextValue | InitializedKibanaContextValue; - -export function isKibanaContextInitialized(arg: any): arg is InitializedKibanaContextValue { - return arg.initialized; -} - -export type SavedSearchQuery = object; - -export const KibanaContext = createContext({ initialized: false }); - -/** - * Custom hook to get the current kibanaContext. - * - * @remarks - * This hook should only be used in components wrapped in `RenderOnlyWithInitializedKibanaContext`, - * otherwise it will throw an error when KibanaContext hasn't been initialized yet. - * In return you get the benefit of not having to check if it's been initialized in the component - * where it's used. - * - * @returns `kibanaContext` - */ -export const useKibanaContext = () => { - const kibanaContext = useContext(KibanaContext); - - if (!isKibanaContextInitialized(kibanaContext)) { - throw new Error('useKibanaContext: kibanaContext not initialized'); - } - - return kibanaContext; -}; - -/** - * Wrapper component to render children only if `kibanaContext` has been initialized. - * In combination with `useKibanaContext` this avoids having to check for the initialization - * in consuming components. - * - * @returns `children` or `null` depending on whether `kibanaContext` is initialized or not. - */ -export const RenderOnlyWithInitializedKibanaContext: FC = ({ children }) => { - const kibanaContext = useContext(KibanaContext); - - return isKibanaContextInitialized(kibanaContext) ? <>{children} : null; -}; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/use_current_index_pattern.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/use_current_index_pattern.ts deleted file mode 100644 index 12c5bde171b8b..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/use_current_index_pattern.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useContext } from 'react'; - -import { isKibanaContextInitialized, KibanaContext } from './kibana_context'; - -export const useCurrentIndexPattern = () => { - const context = useContext(KibanaContext); - - if (!isKibanaContextInitialized(context)) { - throw new Error('useCurrentIndexPattern: kibanaContext not initialized'); - } - - return context.currentIndexPattern; -}; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx index c5c46dcac6c95..4618e96cbfd6e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx @@ -22,6 +22,7 @@ import { } from '@elastic/eui'; import { useApi } from '../../hooks/use_api'; +import { useSearchItems } from '../../hooks/use_search_items'; import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; @@ -29,12 +30,6 @@ import { useAppDependencies, useDocumentationLinks } from '../../app_dependencie import { TransformPivotConfig } from '../../common'; import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; import { PrivilegesWrapper } from '../../lib/authorization'; -import { - getIndexPatternIdByTitle, - loadIndexPatterns, - KibanaProvider, - RenderOnlyWithInitializedKibanaContext, -} from '../../lib/kibana'; import { Wizard } from '../create_transform/components/wizard'; @@ -80,7 +75,12 @@ export const CloneTransformSection: FC = ({ match }) => { const [transformConfig, setTransformConfig] = useState(); const [errorMessage, setErrorMessage] = useState(); const [isInitialized, setIsInitialized] = useState(false); - const [savedObjectId, setSavedObjectId] = useState(undefined); + const { + getIndexPatternIdByTitle, + loadIndexPatterns, + searchItems, + setSavedObjectId, + } = useSearchItems(undefined); const fetchTransformConfig = async () => { try { @@ -169,12 +169,8 @@ export const CloneTransformSection: FC = ({ match }) => {
{JSON.stringify(errorMessage)}
)} - {savedObjectId !== undefined && isInitialized === true && transformConfig !== undefined && ( - - - - - + {searchItems !== undefined && isInitialized === true && transformConfig !== undefined && ( + )} diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/source_index_preview.test.tsx.snap b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/source_index_preview.test.tsx.snap deleted file mode 100644 index e43f2e37bb416..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/__snapshots__/source_index_preview.test.tsx.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` -
- - - -
-`; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx index bfde8f171874e..ddd1a1482fd35 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/expanded_row.test.tsx @@ -39,8 +39,6 @@ describe('Transform: ', () => { }, }; - // Using a wrapping
element because shallow() would fail - // with the Provider being the outer most component. const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx index 16949425284fd..48eff132cd753 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx @@ -4,38 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { KibanaContext } from '../../../../lib/kibana'; +import { createPublicShim } from '../../../../../shim'; +import { getAppProviders } from '../../../../app_dependencies'; import { getPivotQuery } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { SourceIndexPreview } from './source_index_preview'; -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - +jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); describe('Transform: ', () => { test('Minimal initialization', () => { + // Arrange const props = { + indexPattern: { + title: 'the-index-pattern-title', + fields: [] as any[], + } as SearchItems['indexPattern'], query: getPivotQuery('the-query'), }; - - // Using a wrapping
element because shallow() would fail - // with the Provider being the outer most component. - const wrapper = shallow( -
- - - -
+ const Providers = getAppProviders(createPublicShim()); + const { getByText } = render( + + + ); - expect(wrapper).toMatchSnapshot(); + // Act + // Assert + expect(getByText(`Source index ${props.indexPattern.title}`)).toBeInTheDocument(); }); }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx index 0c9dcfb9b1c04..76ed12ff772f5 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx @@ -22,14 +22,13 @@ import { import { getNestedProperty } from '../../../../../../common/utils/object_utils'; -import { useCurrentIndexPattern } from '../../../../lib/kibana'; - import { euiDataGridStyle, euiDataGridToolbarSettings, EsFieldName, PivotQuery, } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { getSourceIndexDevConsoleStatement } from './common'; import { SOURCE_INDEX_STATUS, useSourceIndexData } from './use_source_index_data'; @@ -49,13 +48,13 @@ const SourceIndexPreviewTitle: React.FC = ({ indexPatte ); interface Props { + indexPattern: SearchItems['indexPattern']; query: PivotQuery; } const defaultPagination = { pageIndex: 0, pageSize: 5 }; -export const SourceIndexPreview: React.FC = React.memo(({ query }) => { - const indexPattern = useCurrentIndexPattern(); +export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, query }) => { const allFields = indexPattern.fields.map(f => f.name); const indexPatternFields: string[] = allFields.filter(f => { if (indexPattern.metaFields.includes(f)) { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/__snapshots__/step_create_form.test.tsx.snap b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/__snapshots__/step_create_form.test.tsx.snap deleted file mode 100644 index e034badea9b11..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/__snapshots__/step_create_form.test.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` -
- - - -
-`; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx index 625c545ee8c46..7a22af492e36e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx @@ -4,23 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { KibanaContext } from '../../../../lib/kibana'; +import { createPublicShim } from '../../../../../shim'; +import { getAppProviders } from '../../../../app_dependencies'; import { StepCreateForm } from './step_create_form'; -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - +jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); describe('Transform: ', () => { test('Minimal initialization', () => { + // Arrange const props = { createIndexPattern: false, transformId: 'the-transform-id', @@ -29,16 +27,15 @@ describe('Transform: ', () => { onChange() {}, }; - // Using a wrapping
element because shallow() would fail - // with the Provider being the outer most component. - const wrapper = shallow( -
- - - -
+ const Providers = getAppProviders(createPublicShim()); + const { getByText } = render( + + + ); - expect(wrapper).toMatchSnapshot(); + // Act + // Assert + expect(getByText('Create and start')).toBeInTheDocument(); }); }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index bbeb97b6b8113..4198c2ea0260d 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -34,7 +34,6 @@ import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants import { getTransformProgress, getDiscoverUrl } from '../../../../common'; import { useApi } from '../../../../hooks/use_api'; -import { useKibanaContext } from '../../../../lib/kibana'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { RedirectToTransformManagement } from '../../../../common/navigation'; import { ToastNotificationText } from '../../../../components'; @@ -76,7 +75,8 @@ export const StepCreateForm: FC = React.memo( ); const deps = useAppDependencies(); - const kibanaContext = useKibanaContext(); + const indexPatterns = deps.plugins.data.indexPatterns; + const uiSettings = deps.core.uiSettings; const toastNotifications = useToastNotifications(); useEffect(() => { @@ -176,7 +176,7 @@ export const StepCreateForm: FC = React.memo( const indexPatternName = transformConfig.dest.index; try { - const newIndexPattern = await kibanaContext.indexPatterns.make(); + const newIndexPattern = await indexPatterns.make(); Object.assign(newIndexPattern, { id: '', @@ -200,8 +200,8 @@ export const StepCreateForm: FC = React.memo( // check if there's a default index pattern, if not, // set the newly created one as the default index pattern. - if (!kibanaContext.kibanaConfig.get('defaultIndex')) { - await kibanaContext.kibanaConfig.set('defaultIndex', id); + if (!uiSettings.get('defaultIndex')) { + await uiSettings.set('defaultIndex', id); } toastNotifications.addSuccess( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/pivot_preview.test.tsx.snap b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/pivot_preview.test.tsx.snap deleted file mode 100644 index a7da172a67b8a..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/pivot_preview.test.tsx.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` -
- - - -
-`; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/step_define_form.test.tsx.snap b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/step_define_form.test.tsx.snap deleted file mode 100644 index 70a0bfc12b208..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/step_define_form.test.tsx.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` -
- - - -
-`; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/step_define_summary.test.tsx.snap b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/step_define_summary.test.tsx.snap deleted file mode 100644 index b18233e5c53e3..0000000000000 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/__snapshots__/step_define_summary.test.tsx.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transform: Minimal initialization 1`] = ` -
- - - -
-`; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx index 2ac4295da1eed..464b6e1fd9fe3 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { KibanaContext } from '../../../../lib/kibana'; - +import { createPublicShim } from '../../../../../shim'; +import { getAppProviders } from '../../../../app_dependencies'; import { getPivotQuery, PivotAggsConfig, @@ -16,19 +17,16 @@ import { PIVOT_SUPPORTED_AGGS, PIVOT_SUPPORTED_GROUP_BY_AGGS, } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { PivotPreview } from './pivot_preview'; -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - +jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); describe('Transform: ', () => { test('Minimal initialization', () => { + // Arrange const groupBy: PivotGroupByConfig = { agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, field: 'the-group-by-field', @@ -44,19 +42,22 @@ describe('Transform: ', () => { const props = { aggs: { 'the-agg-name': agg }, groupBy: { 'the-group-by-name': groupBy }, + indexPattern: { + title: 'the-index-pattern-title', + fields: [] as any[], + } as SearchItems['indexPattern'], query: getPivotQuery('the-query'), }; - // Using a wrapping
element because shallow() would fail - // with the Provider being the outer most component. - const wrapper = shallow( -
- - - -
+ const Providers = getAppProviders(createPublicShim()); + const { getByText } = render( + + + ); - expect(wrapper).toMatchSnapshot(); + // Act + // Assert + expect(getByText('Transform pivot preview')).toBeInTheDocument(); }); }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx index b755956eae24e..9b32bbbae839e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx @@ -24,8 +24,6 @@ import { import { dictionaryToArray } from '../../../../../../common/types/common'; import { getNestedProperty } from '../../../../../../common/utils/object_utils'; -import { useCurrentIndexPattern } from '../../../../lib/kibana'; - import { euiDataGridStyle, euiDataGridToolbarSettings, @@ -36,6 +34,7 @@ import { PivotGroupByConfigDict, PivotQuery, } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { getPivotPreviewDevConsoleStatement, multiColumnSortFactory } from './common'; import { PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data'; @@ -103,184 +102,186 @@ const ErrorMessage: FC = ({ message }) => ( interface PivotPreviewProps { aggs: PivotAggsConfigDict; groupBy: PivotGroupByConfigDict; + indexPattern: SearchItems['indexPattern']; query: PivotQuery; } const defaultPagination = { pageIndex: 0, pageSize: 5 }; -export const PivotPreview: FC = React.memo(({ aggs, groupBy, query }) => { - const indexPattern = useCurrentIndexPattern(); - - const { - previewData: data, - previewMappings, - errorMessage, - previewRequest, - status, - } = usePivotPreviewData(indexPattern, query, aggs, groupBy); - const groupByArr = dictionaryToArray(groupBy); - - // Filters mapping properties of type `object`, which get returned for nested field parents. - const columnKeys = Object.keys(previewMappings.properties).filter( - key => previewMappings.properties[key].type !== 'object' - ); - columnKeys.sort(sortColumns(groupByArr)); - - // Column visibility - const [visibleColumns, setVisibleColumns] = useState(columnKeys); - - useEffect(() => { - setVisibleColumns(columnKeys); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(columnKeys)]); - - const [pagination, setPagination] = useState(defaultPagination); - - // Reset pagination if data changes. This is to avoid ending up with an empty table - // when for example the user selected a page that is not available with the updated data. - useEffect(() => { - setPagination(defaultPagination); - }, [data.length]); - - // EuiDataGrid State - const dataGridColumns = columnKeys.map(id => ({ id })); - - const onChangeItemsPerPage = useCallback( - pageSize => { - setPagination(p => { - const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); - return { pageIndex, pageSize }; - }); - }, - [setPagination] - ); +export const PivotPreview: FC = React.memo( + ({ aggs, groupBy, indexPattern, query }) => { + const { + previewData: data, + previewMappings, + errorMessage, + previewRequest, + status, + } = usePivotPreviewData(indexPattern, query, aggs, groupBy); + const groupByArr = dictionaryToArray(groupBy); + + // Filters mapping properties of type `object`, which get returned for nested field parents. + const columnKeys = Object.keys(previewMappings.properties).filter( + key => previewMappings.properties[key].type !== 'object' + ); + columnKeys.sort(sortColumns(groupByArr)); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState(columnKeys); + + useEffect(() => { + setVisibleColumns(columnKeys); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(columnKeys)]); + + const [pagination, setPagination] = useState(defaultPagination); + + // Reset pagination if data changes. This is to avoid ending up with an empty table + // when for example the user selected a page that is not available with the updated data. + useEffect(() => { + setPagination(defaultPagination); + }, [data.length]); + + // EuiDataGrid State + const dataGridColumns = columnKeys.map(id => ({ id })); + + const onChangeItemsPerPage = useCallback( + pageSize => { + setPagination(p => { + const pageIndex = Math.floor((p.pageSize * p.pageIndex) / pageSize); + return { pageIndex, pageSize }; + }); + }, + [setPagination] + ); - const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ - setPagination, - ]); + const onChangePage = useCallback(pageIndex => setPagination(p => ({ ...p, pageIndex })), [ + setPagination, + ]); - // Sorting config - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]); + // Sorting config + const [sortingColumns, setSortingColumns] = useState([]); + const onSort = useCallback(sc => setSortingColumns(sc), [setSortingColumns]); - if (sortingColumns.length > 0) { - data.sort(multiColumnSortFactory(sortingColumns)); - } + if (sortingColumns.length > 0) { + data.sort(multiColumnSortFactory(sortingColumns)); + } - const pageData = data.slice( - pagination.pageIndex * pagination.pageSize, - (pagination.pageIndex + 1) * pagination.pageSize - ); + const pageData = data.slice( + pagination.pageIndex * pagination.pageSize, + (pagination.pageIndex + 1) * pagination.pageSize + ); - const renderCellValue = useMemo(() => { - return ({ - rowIndex, - columnId, - setCellProps, - }: { - rowIndex: number; - columnId: string; - setCellProps: any; - }) => { - const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; - - const cellValue = pageData.hasOwnProperty(adjustedRowIndex) - ? getNestedProperty(pageData[adjustedRowIndex], columnId, null) - : null; - - if (typeof cellValue === 'object' && cellValue !== null) { - return JSON.stringify(cellValue); - } + const renderCellValue = useMemo(() => { + return ({ + rowIndex, + columnId, + setCellProps, + }: { + rowIndex: number; + columnId: string; + setCellProps: any; + }) => { + const adjustedRowIndex = rowIndex - pagination.pageIndex * pagination.pageSize; + + const cellValue = pageData.hasOwnProperty(adjustedRowIndex) + ? getNestedProperty(pageData[adjustedRowIndex], columnId, null) + : null; + + if (typeof cellValue === 'object' && cellValue !== null) { + return JSON.stringify(cellValue); + } - if (cellValue === undefined) { - return null; - } + if (cellValue === undefined) { + return null; + } - return cellValue; - }; - }, [pageData, pagination.pageIndex, pagination.pageSize]); + return cellValue; + }; + }, [pageData, pagination.pageIndex, pagination.pageSize]); + + if (status === PIVOT_PREVIEW_STATUS.ERROR) { + return ( +
+ + + + +
+ ); + } - if (status === PIVOT_PREVIEW_STATUS.ERROR) { - return ( -
- - - - -
- ); - } + if (data.length === 0) { + let noDataMessage = i18n.translate( + 'xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', + { + defaultMessage: + 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', + } + ); - if (data.length === 0) { - let noDataMessage = i18n.translate( - 'xpack.transform.pivotPreview.PivotPreviewNoDataCalloutBody', - { - defaultMessage: - 'The preview request did not return any data. Please ensure the optional query returns data and that values exist for the field used by group-by and aggregation fields.', + const aggsArr = dictionaryToArray(aggs); + if (aggsArr.length === 0 || groupByArr.length === 0) { + noDataMessage = i18n.translate( + 'xpack.transform.pivotPreview.PivotPreviewIncompleteConfigCalloutBody', + { + defaultMessage: 'Please choose at least one group-by field and aggregation.', + } + ); } - ); - const aggsArr = dictionaryToArray(aggs); - if (aggsArr.length === 0 || groupByArr.length === 0) { - noDataMessage = i18n.translate( - 'xpack.transform.pivotPreview.PivotPreviewIncompleteConfigCalloutBody', - { - defaultMessage: 'Please choose at least one group-by field and aggregation.', - } + return ( +
+ + +

{noDataMessage}

+
+
); } + + if (columnKeys.length === 0) { + return null; + } + return ( -
+
- -

{noDataMessage}

-
+
+ {status === PIVOT_PREVIEW_STATUS.LOADING && } + {status !== PIVOT_PREVIEW_STATUS.LOADING && ( + + )} +
+ {dataGridColumns.length > 0 && data.length > 0 && ( + + )}
); } - - if (columnKeys.length === 0) { - return null; - } - - return ( -
- -
- {status === PIVOT_PREVIEW_STATUS.LOADING && } - {status !== PIVOT_PREVIEW_STATUS.LOADING && ( - - )} -
- {dataGridColumns.length > 0 && data.length > 0 && ( - - )} -
- ); -}); +); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 44edd1340e8d6..f45ef7cfddbf9 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -4,40 +4,44 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { KibanaContext } from '../../../../lib/kibana'; - +import { createPublicShim } from '../../../../../shim'; +import { getAppProviders } from '../../../../app_dependencies'; import { PivotAggsConfigDict, PivotGroupByConfigDict, PIVOT_SUPPORTED_AGGS, PIVOT_SUPPORTED_GROUP_BY_AGGS, } from '../../../../common'; -import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form'; +import { SearchItems } from '../../../../hooks/use_search_items'; -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); +import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form'; +jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); describe('Transform: ', () => { test('Minimal initialization', () => { - // Using a wrapping
element because shallow() would fail - // with the Provider being the outer most component. - const wrapper = shallow( -
- - {}} /> - -
+ // Arrange + const searchItems = { + indexPattern: { + title: 'the-index-pattern-title', + fields: [] as any[], + } as SearchItems['indexPattern'], + }; + const Providers = getAppProviders(createPublicShim()); + const { getByLabelText } = render( + + + ); - expect(wrapper).toMatchSnapshot(); + // Act + // Assert + expect(getByLabelText('Index pattern')).toBeInTheDocument(); }); }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 9b96e4b1ee758..f61f54c38680e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -26,6 +26,7 @@ import { EuiSwitch, } from '@elastic/eui'; +import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items'; import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode'; import { useDocumentationLinks, useToastNotifications } from '../../../../app_dependencies'; import { TransformPivotConfig } from '../../../../common'; @@ -38,12 +39,6 @@ import { PivotPreview } from './pivot_preview'; import { KqlFilterBar } from '../../../../../shared_imports'; import { SwitchModal } from './switch_modal'; -import { - useKibanaContext, - InitializedKibanaContextValue, - SavedSearchQuery, -} from '../../../../lib/kibana'; - import { getPivotQuery, getPreviewRequestBody, @@ -78,18 +73,14 @@ export interface StepDefineExposedState { const defaultSearch = '*'; const emptySearch = ''; -export function getDefaultStepDefineState( - kibanaContext: InitializedKibanaContextValue -): StepDefineExposedState { +export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineExposedState { return { aggList: {} as PivotAggsConfigDict, groupByList: {} as PivotGroupByConfigDict, isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, - searchString: - kibanaContext.currentSavedSearch !== undefined ? kibanaContext.combinedQuery : defaultSearch, - searchQuery: - kibanaContext.currentSavedSearch !== undefined ? kibanaContext.combinedQuery : defaultSearch, + searchString: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, + searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, sourceConfigUpdated: false, valid: false, }; @@ -242,14 +233,14 @@ export function getAggNameConflictToastMessages( interface Props { overrides?: StepDefineExposedState; onChange(s: StepDefineExposedState): void; + searchItems: SearchItems; } -export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange }) => { - const kibanaContext = useKibanaContext(); +export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, searchItems }) => { const toastNotifications = useToastNotifications(); const { esQueryDsl, esTransformPivot } = useDocumentationLinks(); - const defaults = { ...getDefaultStepDefineState(kibanaContext), ...overrides }; + const defaults = { ...getDefaultStepDefineState(searchItems), ...overrides }; // The search filter const [searchString, setSearchString] = useState(defaults.searchString); @@ -267,7 +258,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange // The list of selected group by fields const [groupByList, setGroupByList] = useState(defaults.groupByList); - const indexPattern = kibanaContext.currentIndexPattern; + const { indexPattern } = searchItems; const { groupByOptions, @@ -568,7 +559,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
- {kibanaContext.currentSavedSearch === undefined && typeof searchString === 'string' && ( + {searchItems.savedSearch === undefined && typeof searchString === 'string' && ( = React.memo(({ overrides = {}, onChange )} - {kibanaContext.currentSavedSearch === undefined && ( + {searchItems.savedSearch === undefined && ( @@ -720,16 +711,15 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange )} - {kibanaContext.currentSavedSearch !== undefined && - kibanaContext.currentSavedSearch.id !== undefined && ( - - {kibanaContext.currentSavedSearch.title} - - )} + {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( + + {searchItems.savedSearch.title} + + )} {!isAdvancedPivotEditorEnabled && ( @@ -903,9 +893,14 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange - + - + ); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx index 78f6fc30f9191..0f7da50bbbade 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx @@ -4,30 +4,35 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; import React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; -import { KibanaContext } from '../../../../lib/kibana'; - +import { createPublicShim } from '../../../../../shim'; +import { getAppProviders } from '../../../../app_dependencies'; import { PivotAggsConfig, PivotGroupByConfig, PIVOT_SUPPORTED_AGGS, PIVOT_SUPPORTED_GROUP_BY_AGGS, } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; + import { StepDefineExposedState } from './step_define_form'; import { StepDefineSummary } from './step_define_summary'; -// workaround to make React.memo() work with enzyme -jest.mock('react', () => { - const r = jest.requireActual('react'); - return { ...r, memo: (x: any) => x }; -}); - +jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); describe('Transform: ', () => { test('Minimal initialization', () => { + // Arrange + const searchItems = { + indexPattern: { + title: 'the-index-pattern-title', + fields: [] as any[], + } as SearchItems['indexPattern'], + }; const groupBy: PivotGroupByConfig = { agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, field: 'the-group-by-field', @@ -40,7 +45,7 @@ describe('Transform: ', () => { aggName: 'the-group-by-agg-name', dropDownName: 'the-group-by-drop-down-name', }; - const props: StepDefineExposedState = { + const formState: StepDefineExposedState = { aggList: { 'the-agg-name': agg }, groupByList: { 'the-group-by-name': groupBy }, isAdvancedPivotEditorEnabled: false, @@ -51,16 +56,16 @@ describe('Transform: ', () => { valid: true, }; - // Using a wrapping
element because shallow() would fail - // with the Provider being the outer most component. - const wrapper = shallow( -
- - - -
+ const Providers = getAppProviders(createPublicShim()); + const { getByText } = render( + + + ); - expect(wrapper).toMatchSnapshot(); + // Act + // Assert + expect(getByText('Group by')).toBeInTheDocument(); + expect(getByText('Aggregations')).toBeInTheDocument(); }); }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index 30c447f62c760..f8fb9db9bd686 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -17,26 +17,27 @@ import { EuiText, } from '@elastic/eui'; -import { useKibanaContext } from '../../../../lib/kibana'; +import { getPivotQuery } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { AggListSummary } from '../aggregation_list'; import { GroupByListSummary } from '../group_by_list'; -import { PivotPreview } from './pivot_preview'; -import { getPivotQuery } from '../../../../common'; +import { PivotPreview } from './pivot_preview'; import { StepDefineExposedState } from './step_define_form'; const defaultSearch = '*'; const emptySearch = ''; -export const StepDefineSummary: FC = ({ - searchString, - searchQuery, - groupByList, - aggList, -}) => { - const kibanaContext = useKibanaContext(); +interface Props { + formState: StepDefineExposedState; + searchItems: SearchItems; +} +export const StepDefineSummary: FC = ({ + formState: { searchString, searchQuery, groupByList, aggList }, + searchItems, +}) => { const pivotQuery = getPivotQuery(searchQuery); let useCodeBlock = false; let displaySearch; @@ -55,8 +56,8 @@ export const StepDefineSummary: FC = ({
- {kibanaContext.currentSavedSearch !== undefined && - kibanaContext.currentSavedSearch.id === undefined && + {searchItems.savedSearch !== undefined && + searchItems.savedSearch.id === undefined && typeof searchString === 'string' && ( = ({ defaultMessage: 'Index pattern', })} > - {kibanaContext.currentIndexPattern.title} + {searchItems.indexPattern.title} {useCodeBlock === false && displaySearch !== emptySearch && ( = ({ )} - {kibanaContext.currentSavedSearch !== undefined && - kibanaContext.currentSavedSearch.id !== undefined && ( - - {kibanaContext.currentSavedSearch.title} - - )} + {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( + + {searchItems.savedSearch.title} + + )} = ({ - + diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 5ae2180bfe779..ea9483af49302 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -11,11 +11,15 @@ import { i18n } from '@kbn/i18n'; import { EuiLink, EuiSwitch, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui'; import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public'; -import { useKibanaContext } from '../../../../lib/kibana'; import { isValidIndexName } from '../../../../../../common/utils/es_utils'; -import { useDocumentationLinks, useToastNotifications } from '../../../../app_dependencies'; +import { + useAppDependencies, + useDocumentationLinks, + useToastNotifications, +} from '../../../../app_dependencies'; import { ToastNotificationText } from '../../../../components'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { useApi } from '../../../../hooks/use_api'; import { isTransformIdValid, TransformId, TransformPivotConfig } from '../../../../common'; @@ -67,109 +71,129 @@ export function applyTransformConfigToDetailsState( interface Props { overrides?: StepDetailsExposedState; onChange(s: StepDetailsExposedState): void; + searchItems: SearchItems; } -export const StepDetailsForm: FC = React.memo(({ overrides = {}, onChange }) => { - const kibanaContext = useKibanaContext(); - const toastNotifications = useToastNotifications(); - const { esIndicesCreateIndex } = useDocumentationLinks(); +export const StepDetailsForm: FC = React.memo( + ({ overrides = {}, onChange, searchItems }) => { + const deps = useAppDependencies(); + const toastNotifications = useToastNotifications(); + const { esIndicesCreateIndex } = useDocumentationLinks(); - const defaults = { ...getDefaultStepDetailsState(), ...overrides }; + const defaults = { ...getDefaultStepDetailsState(), ...overrides }; - const [transformId, setTransformId] = useState(defaults.transformId); - const [transformDescription, setTransformDescription] = useState( - defaults.transformDescription - ); - const [destinationIndex, setDestinationIndex] = useState(defaults.destinationIndex); - const [transformIds, setTransformIds] = useState([]); - const [indexNames, setIndexNames] = useState([]); - const [indexPatternTitles, setIndexPatternTitles] = useState([]); - const [createIndexPattern, setCreateIndexPattern] = useState(defaults.createIndexPattern); + const [transformId, setTransformId] = useState(defaults.transformId); + const [transformDescription, setTransformDescription] = useState( + defaults.transformDescription + ); + const [destinationIndex, setDestinationIndex] = useState( + defaults.destinationIndex + ); + const [transformIds, setTransformIds] = useState([]); + const [indexNames, setIndexNames] = useState([]); + const [indexPatternTitles, setIndexPatternTitles] = useState([]); + const [createIndexPattern, setCreateIndexPattern] = useState(defaults.createIndexPattern); - // Continuous mode state - const [isContinuousModeEnabled, setContinuousModeEnabled] = useState( - defaults.isContinuousModeEnabled - ); + // Continuous mode state + const [isContinuousModeEnabled, setContinuousModeEnabled] = useState( + defaults.isContinuousModeEnabled + ); - const api = useApi(); + const api = useApi(); - // fetch existing transform IDs and indices once for form validation - useEffect(() => { - // use an IIFE to avoid returning a Promise to useEffect. - (async function() { - try { - setTransformIds( - (await api.getTransforms()).transforms.map( - (transform: TransformPivotConfig) => transform.id - ) - ); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { - defaultMessage: 'An error occurred getting the existing transform IDs:', - }), - text: toMountPoint(), - }); - } + // fetch existing transform IDs and indices once for form validation + useEffect(() => { + // use an IIFE to avoid returning a Promise to useEffect. + (async function() { + try { + setTransformIds( + (await api.getTransforms()).transforms.map( + (transform: TransformPivotConfig) => transform.id + ) + ); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { + defaultMessage: 'An error occurred getting the existing transform IDs:', + }), + text: toMountPoint(), + }); + } - try { - setIndexNames((await api.getIndices()).map(index => index.name)); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { - defaultMessage: 'An error occurred getting the existing index names:', - }), - text: toMountPoint(), - }); - } + try { + setIndexNames((await api.getIndices()).map(index => index.name)); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { + defaultMessage: 'An error occurred getting the existing index names:', + }), + text: toMountPoint(), + }); + } - try { - setIndexPatternTitles(await kibanaContext.indexPatterns.getTitles()); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexPatternTitles', { - defaultMessage: 'An error occurred getting the existing index pattern titles:', - }), - text: toMountPoint(), - }); - } - })(); - // custom comparison - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [kibanaContext.initialized]); + try { + setIndexPatternTitles(await deps.plugins.data.indexPatterns.getTitles()); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.transform.stepDetailsForm.errorGettingIndexPatternTitles', + { + defaultMessage: 'An error occurred getting the existing index pattern titles:', + } + ), + text: toMountPoint(), + }); + } + })(); + // run once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - const dateFieldNames = kibanaContext.currentIndexPattern.fields - .filter(f => f.type === 'date') - .map(f => f.name) - .sort(); - const isContinuousModeAvailable = dateFieldNames.length > 0; - const [continuousModeDateField, setContinuousModeDateField] = useState( - isContinuousModeAvailable ? dateFieldNames[0] : '' - ); - const [continuousModeDelay, setContinuousModeDelay] = useState(defaults.continuousModeDelay); - const isContinuousModeDelayValid = delayValidator(continuousModeDelay); + const dateFieldNames = searchItems.indexPattern.fields + .filter(f => f.type === 'date') + .map(f => f.name) + .sort(); + const isContinuousModeAvailable = dateFieldNames.length > 0; + const [continuousModeDateField, setContinuousModeDateField] = useState( + isContinuousModeAvailable ? dateFieldNames[0] : '' + ); + const [continuousModeDelay, setContinuousModeDelay] = useState(defaults.continuousModeDelay); + const isContinuousModeDelayValid = delayValidator(continuousModeDelay); - const transformIdExists = transformIds.some(id => transformId === id); - const transformIdEmpty = transformId === ''; - const transformIdValid = isTransformIdValid(transformId); + const transformIdExists = transformIds.some(id => transformId === id); + const transformIdEmpty = transformId === ''; + const transformIdValid = isTransformIdValid(transformId); - const indexNameExists = indexNames.some(name => destinationIndex === name); - const indexNameEmpty = destinationIndex === ''; - const indexNameValid = isValidIndexName(destinationIndex); - const indexPatternTitleExists = indexPatternTitles.some(name => destinationIndex === name); + const indexNameExists = indexNames.some(name => destinationIndex === name); + const indexNameEmpty = destinationIndex === ''; + const indexNameValid = isValidIndexName(destinationIndex); + const indexPatternTitleExists = indexPatternTitles.some(name => destinationIndex === name); - const valid = - !transformIdEmpty && - transformIdValid && - !transformIdExists && - !indexNameEmpty && - indexNameValid && - (!indexPatternTitleExists || !createIndexPattern) && - (!isContinuousModeAvailable || (isContinuousModeAvailable && isContinuousModeDelayValid)); + const valid = + !transformIdEmpty && + transformIdValid && + !transformIdExists && + !indexNameEmpty && + indexNameValid && + (!indexPatternTitleExists || !createIndexPattern) && + (!isContinuousModeAvailable || (isContinuousModeAvailable && isContinuousModeDelayValid)); - // expose state to wizard - useEffect(() => { - onChange({ + // expose state to wizard + useEffect(() => { + onChange({ + continuousModeDateField, + continuousModeDelay, + createIndexPattern, + isContinuousModeEnabled, + transformId, + transformDescription, + destinationIndex, + touched: true, + valid, + }); + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ + }, [ continuousModeDateField, continuousModeDelay, createIndexPattern, @@ -177,232 +201,223 @@ export const StepDetailsForm: FC = React.memo(({ overrides = {}, onChange transformId, transformDescription, destinationIndex, - touched: true, valid, - }); - // custom comparison - /* eslint-disable react-hooks/exhaustive-deps */ - }, [ - continuousModeDateField, - continuousModeDelay, - createIndexPattern, - isContinuousModeEnabled, - transformId, - transformDescription, - destinationIndex, - valid, - /* eslint-enable react-hooks/exhaustive-deps */ - ]); + /* eslint-enable react-hooks/exhaustive-deps */ + ]); - return ( -
- - - setTransformId(e.target.value)} - aria-label={i18n.translate( - 'xpack.transform.stepDetailsForm.transformIdInputAriaLabel', - { - defaultMessage: 'Choose a unique transform ID.', - } - )} + return ( +
+ + - - - setTransformDescription(e.target.value)} - aria-label={i18n.translate( - 'xpack.transform.stepDetailsForm.transformDescriptionInputAriaLabel', - { - defaultMessage: 'Choose an optional transform description.', - } - )} - data-test-subj="transformDescriptionInput" - /> - - - {i18n.translate('xpack.transform.stepDetailsForm.destinationIndexInvalidError', { - defaultMessage: 'Invalid destination index name.', - })} -
- - {i18n.translate( - 'xpack.transform.stepDetailsForm.destinationIndexInvalidErrorLink', - { - defaultMessage: 'Learn more about index name limitations.', - } - )} - - , - ] - } - > - setDestinationIndex(e.target.value)} - aria-label={i18n.translate( - 'xpack.transform.stepDetailsForm.destinationIndexInputAriaLabel', + error={[ + ...(!transformIdEmpty && !transformIdValid + ? [ + i18n.translate('xpack.transform.stepDetailsForm.transformIdInvalidError', { + defaultMessage: + 'Must contain lowercase alphanumeric characters (a-z and 0-9), hyphens, and underscores only and must start and end with alphanumeric characters.', + }), + ] + : []), + ...(transformIdExists + ? [ + i18n.translate('xpack.transform.stepDetailsForm.transformIdExistsError', { + defaultMessage: 'A transform with this ID already exists.', + }), + ] + : []), + ]} + > + setTransformId(e.target.value)} + aria-label={i18n.translate( + 'xpack.transform.stepDetailsForm.transformIdInputAriaLabel', + { + defaultMessage: 'Choose a unique transform ID.', + } + )} + isInvalid={(!transformIdEmpty && !transformIdValid) || transformIdExists} + data-test-subj="transformIdInput" + /> +
+ - - - setCreateIndexPattern(!createIndexPattern)} - data-test-subj="transformCreateIndexPatternSwitch" - /> - - - setContinuousModeEnabled(!isContinuousModeEnabled)} - disabled={isContinuousModeAvailable === false} - data-test-subj="transformContinuousModeSwitch" - /> - - {isContinuousModeEnabled && ( - - + setTransformDescription(e.target.value)} + aria-label={i18n.translate( + 'xpack.transform.stepDetailsForm.transformDescriptionInputAriaLabel', { - defaultMessage: 'Date field', + defaultMessage: 'Choose an optional transform description.', } )} - helpText={i18n.translate( - 'xpack.transform.stepDetailsForm.continuousModeDateFieldHelpText', + data-test-subj="transformDescriptionInput" + /> + + + {i18n.translate('xpack.transform.stepDetailsForm.destinationIndexInvalidError', { + defaultMessage: 'Invalid destination index name.', + })} +
+ + {i18n.translate( + 'xpack.transform.stepDetailsForm.destinationIndexInvalidErrorLink', + { + defaultMessage: 'Learn more about index name limitations.', + } + )} + +
, + ] + } + > + setDestinationIndex(e.target.value)} + aria-label={i18n.translate( + 'xpack.transform.stepDetailsForm.destinationIndexInputAriaLabel', { - defaultMessage: - 'Select the date field that can be used to identify new documents.', + defaultMessage: 'Choose a unique destination index name.', } )} - > - ({ text }))} - value={continuousModeDateField} - onChange={e => setContinuousModeDateField(e.target.value)} - data-test-subj="transformContinuousDateFieldSelect" - /> - - + + + - setContinuousModeDelay(e.target.value)} - aria-label={i18n.translate( - 'xpack.transform.stepDetailsForm.continuousModeAriaLabel', + checked={createIndexPattern === true} + onChange={() => setCreateIndexPattern(!createIndexPattern)} + data-test-subj="transformCreateIndexPatternSwitch" + /> + + + setContinuousModeEnabled(!isContinuousModeEnabled)} + disabled={isContinuousModeAvailable === false} + data-test-subj="transformContinuousModeSwitch" + /> + + {isContinuousModeEnabled && ( + + + ({ text }))} + value={continuousModeDateField} + onChange={e => setContinuousModeDateField(e.target.value)} + data-test-subj="transformContinuousDateFieldSelect" + /> + + - - - )} -
-
- ); -}); + error={ + !isContinuousModeDelayValid && [ + i18n.translate('xpack.transform.stepDetailsForm.continuousModeDelayError', { + defaultMessage: 'Invalid delay format', + }), + ] + } + helpText={i18n.translate( + 'xpack.transform.stepDetailsForm.continuousModeDelayHelpText', + { + defaultMessage: 'Time delay between current time and latest input data time.', + } + )} + > + setContinuousModeDelay(e.target.value)} + aria-label={i18n.translate( + 'xpack.transform.stepDetailsForm.continuousModeAriaLabel', + { + defaultMessage: 'Choose a delay.', + } + )} + isInvalid={!isContinuousModeDelayValid} + data-test-subj="transformContinuousDelayInput" + /> +
+ + )} +
+
+ ); + } +); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index f1861755d9742..0773ecbb1d8d3 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -10,9 +10,8 @@ import { i18n } from '@kbn/i18n'; import { EuiSteps, EuiStepStatus } from '@elastic/eui'; -import { useKibanaContext } from '../../../../lib/kibana'; - import { getCreateRequestBody, TransformPivotConfig } from '../../../../common'; +import { SearchItems } from '../../../../hooks/use_search_items'; import { applyTransformConfigToDefineState, @@ -46,6 +45,7 @@ interface DefinePivotStepProps { stepDefineState: StepDefineExposedState; setCurrentStep: React.Dispatch>; setStepDefineState: React.Dispatch>; + searchItems: SearchItems; } const StepDefine: FC = ({ @@ -53,6 +53,7 @@ const StepDefine: FC = ({ stepDefineState, setCurrentStep, setStepDefineState, + searchItems, }) => { const definePivotRef = useRef(null); @@ -61,31 +62,36 @@ const StepDefine: FC = ({
{isCurrentStep && ( - + setCurrentStep(WIZARD_STEPS.DETAILS)} nextActive={stepDefineState.valid} /> )} - {!isCurrentStep && } + {!isCurrentStep && ( + + )} ); }; interface WizardProps { cloneConfig?: TransformPivotConfig; + searchItems: SearchItems; } -export const Wizard: FC = React.memo(({ cloneConfig }) => { - const kibanaContext = useKibanaContext(); - +export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) => { // The current WIZARD_STEP const [currentStep, setCurrentStep] = useState(WIZARD_STEPS.DEFINE); // The DEFINE state const [stepDefineState, setStepDefineState] = useState( - applyTransformConfigToDefineState(getDefaultStepDefineState(kibanaContext), cloneConfig) + applyTransformConfigToDefineState(getDefaultStepDefineState(searchItems), cloneConfig) ); // The DETAILS state @@ -95,7 +101,11 @@ export const Wizard: FC = React.memo(({ cloneConfig }) => { const stepDetails = currentStep === WIZARD_STEPS.DETAILS ? ( - + ) : ( ); @@ -122,7 +132,7 @@ export const Wizard: FC = React.memo(({ cloneConfig }) => { } }, []); - const indexPattern = kibanaContext.currentIndexPattern; + const { indexPattern } = searchItems; const transformConfig = getCreateRequestBody( indexPattern.title, @@ -154,6 +164,7 @@ export const Wizard: FC = React.memo(({ cloneConfig }) => { stepDefineState={stepDefineState} setCurrentStep={setCurrentStep} setStepDefineState={setStepDefineState} + searchItems={searchItems} /> ), }, diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx index 5196f281adf0a..d09fc0913590e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx @@ -22,9 +22,9 @@ import { import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; import { useDocumentationLinks } from '../../app_dependencies'; +import { useSearchItems } from '../../hooks/use_search_items'; import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; import { PrivilegesWrapper } from '../../lib/authorization'; -import { KibanaProvider, RenderOnlyWithInitializedKibanaContext } from '../../lib/kibana'; import { Wizard } from './components/wizard'; @@ -38,43 +38,41 @@ export const CreateTransformSection: FC = ({ match }) => { const { esTransform } = useDocumentationLinks(); + const { searchItems } = useSearchItems(match.params.savedObjectId); + return ( - - - - - -

- -

-
- - - - - -
-
- - - - - - -
-
+ + + + +

+ +

+
+ + + + + +
+
+ + + {searchItems !== undefined && } + +
); }; diff --git a/x-pack/legacy/plugins/transform/public/plugin.ts b/x-pack/legacy/plugins/transform/public/plugin.ts index 23fad00fb0786..7b5fbbb4a2151 100644 --- a/x-pack/legacy/plugins/transform/public/plugin.ts +++ b/x-pack/legacy/plugins/transform/public/plugin.ts @@ -11,7 +11,6 @@ import { breadcrumbService } from './app/services/navigation'; import { docTitleService } from './app/services/navigation'; import { textService } from './app/services/text'; import { uiMetricService } from './app/services/ui_metric'; -import { createSavedSearchesLoader } from '../../../../../src/plugins/discover/public'; export class Plugin { public start(core: ShimCore, plugins: ShimPlugins): void { @@ -27,7 +26,7 @@ export class Plugin { savedObjects, overlays, } = core; - const { data, management, savedSearches: coreSavedSearches, uiMetric, xsrfToken } = plugins; + const { data, management, uiMetric, xsrfToken } = plugins; // AppCore/AppPlugins to be passed on as React context const appDependencies = { @@ -46,7 +45,6 @@ export class Plugin { plugins: { data, management, - savedSearches: coreSavedSearches, xsrfToken, }, }; @@ -61,14 +59,6 @@ export class Plugin { }), order: 3, mount(params) { - const savedSearches = createSavedSearchesLoader({ - savedObjectsClient: core.savedObjects.client, - indexPatterns: plugins.data.indexPatterns, - chrome: core.chrome, - overlays: core.overlays, - }); - coreSavedSearches.setClient(savedSearches); - breadcrumbService.setup(params.setBreadcrumbs); params.setBreadcrumbs([ { diff --git a/x-pack/legacy/plugins/transform/public/shared_imports.ts b/x-pack/legacy/plugins/transform/public/shared_imports.ts index b077cd8836c4b..1ca71f8c4aa77 100644 --- a/x-pack/legacy/plugins/transform/public/shared_imports.ts +++ b/x-pack/legacy/plugins/transform/public/shared_imports.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export { createSavedSearchesLoader } from '../../../../../src/plugins/discover/public'; export { XJsonMode } from '../../../../plugins/es_ui_shared/console_lang/ace/modes/x_json'; export { collapseLiteralStrings, diff --git a/x-pack/legacy/plugins/transform/public/shim.ts b/x-pack/legacy/plugins/transform/public/shim.ts index 05f7626e25e9d..9941aabcf3255 100644 --- a/x-pack/legacy/plugins/transform/public/shim.ts +++ b/x-pack/legacy/plugins/transform/public/shim.ts @@ -13,7 +13,6 @@ import { docTitle } from 'ui/doc_title/doc_title'; import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public'; import { TRANSFORM_DOC_PATHS } from './app/constants'; -import { SavedSearchLoader } from '../../../../../src/plugins/discover/public'; export type NpCore = typeof npStart.core; export type NpPlugins = typeof npStart.plugins; @@ -33,7 +32,7 @@ export type AppCore = Pick< | 'overlays' | 'notifications' >; -export type AppPlugins = Pick; +export type AppPlugins = Pick; export interface AppDependencies { core: AppCore; @@ -61,18 +60,10 @@ export interface ShimPlugins extends NpPlugins { uiMetric: { createUiStatsReporter: typeof createUiStatsReporter; }; - savedSearches: { - getClient(): any; - setClient(client: any): void; - }; xsrfToken: string; } export function createPublicShim(): { core: ShimCore; plugins: ShimPlugins } { - // This is an Angular service, which is why we use this provider pattern - // to access it within our React app. - let savedSearches: SavedSearchLoader; - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = npStart.core.docLinks; return { @@ -94,12 +85,6 @@ export function createPublicShim(): { core: ShimCore; plugins: ShimPlugins } { }, plugins: { ...npStart.plugins, - savedSearches: { - setClient: (client: any): void => { - savedSearches = client; - }, - getClient: (): any => savedSearches, - }, uiMetric: { createUiStatsReporter, },