From 2b4a9fd7d7d57723cede7c73e14fc932a4c148fe Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 27 Feb 2020 12:08:08 +0100 Subject: [PATCH 01/28] [State Management] State syncing helpers for query service I (#56128) Before this pr: Discover, Visualise and Dashboard in setup phase create own state containers which watch for pinned filters, time and refresh interval changes. This watching and state comparisons happen for each plugin separately and not only when a specific app is mounted. So we ended up with a bunch of similar synchronous work which happens every time query services state changes. After this pr: Query service exposes observable to watch for changes (state$). Discover, Visualise and Dashboard use this observable for sub url tracking instead of creating its own. --- .../state_containers_examples/common/index.ts | 21 + .../state_containers_examples/kibana.json | 4 +- .../public/plugin.ts | 23 +- .../public/{ => todo}/app.tsx | 0 .../public/{ => todo}/todo.tsx | 6 +- .../public/with_data_services/application.tsx | 49 ++ .../with_data_services/components/app.tsx | 243 +++++++++ .../public/with_data_services/types.ts | 31 ++ .../state_containers_examples/server/index.ts | 28 ++ .../server/plugin.ts | 56 +++ .../server/routes/index.ts | 36 ++ .../state_containers_examples/server/types.ts | 23 + .../state_containers_examples/tsconfig.json | 1 + .../np_ready/dashboard_app_controller.tsx | 31 +- .../public/dashboard/np_ready/legacy_app.js | 6 +- .../kibana/public/dashboard/plugin.ts | 22 +- .../kibana/public/discover/plugin.ts | 17 +- .../kibana/public/visualize/plugin.ts | 17 +- .../new_platform/new_platform.karma_mock.js | 1 + src/plugins/data/public/index.ts | 6 +- src/plugins/data/public/query/mocks.ts | 7 +- .../data/public/query/query_service.ts | 11 + .../data/public/query/state_sync/README.md | 3 + .../state_sync/connect_to_query_state.test.ts | 465 ++++++++++++++++++ .../state_sync/connect_to_query_state.ts | 194 ++++++++ .../create_global_query_observable.ts | 87 ++++ .../data/public/query/state_sync/index.ts | 5 +- .../query/state_sync/sync_app_filters.test.ts | 197 -------- .../query/state_sync/sync_app_filters.ts | 65 --- .../public/query/state_sync/sync_query.ts | 188 ------- ...ry.test.ts => sync_state_with_url.test.ts} | 95 +--- .../query/state_sync/sync_state_with_url.ts | 102 ++++ .../data/public/query/state_sync/types.ts | 38 ++ .../public/query/timefilter/timefilter.ts | 13 + .../timefilter/timefilter_service.mock.ts | 2 + .../query_string_input.test.tsx.snap | 30 ++ .../ui/search_bar/create_search_bar.tsx | 46 +- .../__snapshots__/zeek_details.test.tsx.snap | 5 + 38 files changed, 1581 insertions(+), 593 deletions(-) create mode 100644 examples/state_containers_examples/common/index.ts rename examples/state_containers_examples/public/{ => todo}/app.tsx (100%) rename examples/state_containers_examples/public/{ => todo}/todo.tsx (98%) create mode 100644 examples/state_containers_examples/public/with_data_services/application.tsx create mode 100644 examples/state_containers_examples/public/with_data_services/components/app.tsx create mode 100644 examples/state_containers_examples/public/with_data_services/types.ts create mode 100644 examples/state_containers_examples/server/index.ts create mode 100644 examples/state_containers_examples/server/plugin.ts create mode 100644 examples/state_containers_examples/server/routes/index.ts create mode 100644 examples/state_containers_examples/server/types.ts create mode 100644 src/plugins/data/public/query/state_sync/README.md create mode 100644 src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts create mode 100644 src/plugins/data/public/query/state_sync/connect_to_query_state.ts create mode 100644 src/plugins/data/public/query/state_sync/create_global_query_observable.ts delete mode 100644 src/plugins/data/public/query/state_sync/sync_app_filters.test.ts delete mode 100644 src/plugins/data/public/query/state_sync/sync_app_filters.ts delete mode 100644 src/plugins/data/public/query/state_sync/sync_query.ts rename src/plugins/data/public/query/state_sync/{sync_query.test.ts => sync_state_with_url.test.ts} (63%) create mode 100644 src/plugins/data/public/query/state_sync/sync_state_with_url.ts create mode 100644 src/plugins/data/public/query/state_sync/types.ts diff --git a/examples/state_containers_examples/common/index.ts b/examples/state_containers_examples/common/index.ts new file mode 100644 index 00000000000000..25dc2eacf9c75c --- /dev/null +++ b/examples/state_containers_examples/common/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const PLUGIN_ID = 'stateContainersExampleWithDataServices'; +export const PLUGIN_NAME = 'State containers example - with data services'; diff --git a/examples/state_containers_examples/kibana.json b/examples/state_containers_examples/kibana.json index 9114a414a4da39..437e9a4fac63c2 100644 --- a/examples/state_containers_examples/kibana.json +++ b/examples/state_containers_examples/kibana.json @@ -3,8 +3,8 @@ "version": "0.0.1", "kibanaVersion": "kibana", "configPath": ["state_containers_examples"], - "server": false, + "server": true, "ui": true, - "requiredPlugins": [], + "requiredPlugins": ["navigation", "data"], "optionalPlugins": [] } diff --git a/examples/state_containers_examples/public/plugin.ts b/examples/state_containers_examples/public/plugin.ts index beb7b93dbc5b66..38ebf315789c09 100644 --- a/examples/state_containers_examples/public/plugin.ts +++ b/examples/state_containers_examples/public/plugin.ts @@ -18,14 +18,16 @@ */ import { AppMountParameters, CoreSetup, Plugin } from 'kibana/public'; +import { AppPluginDependencies } from './with_data_services/types'; +import { PLUGIN_ID, PLUGIN_NAME } from '../common'; export class StateContainersExamplesPlugin implements Plugin { public setup(core: CoreSetup) { core.application.register({ - id: 'state-containers-example-browser-history', + id: 'stateContainersExampleBrowserHistory', title: 'State containers example - browser history routing', async mount(params: AppMountParameters) { - const { renderApp, History } = await import('./app'); + const { renderApp, History } = await import('./todo/app'); return renderApp(params, { appInstanceId: '1', appTitle: 'Routing with browser history', @@ -34,10 +36,10 @@ export class StateContainersExamplesPlugin implements Plugin { }, }); core.application.register({ - id: 'state-containers-example-hash-history', + id: 'stateContainersExampleHashHistory', title: 'State containers example - hash history routing', async mount(params: AppMountParameters) { - const { renderApp, History } = await import('./app'); + const { renderApp, History } = await import('./todo/app'); return renderApp(params, { appInstanceId: '2', appTitle: 'Routing with hash history', @@ -45,6 +47,19 @@ export class StateContainersExamplesPlugin implements Plugin { }); }, }); + + core.application.register({ + id: PLUGIN_ID, + title: PLUGIN_NAME, + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./with_data_services/application'); + // Get start services as specified in kibana.json + const [coreStart, depsStart] = await core.getStartServices(); + // Render the application + return renderApp(coreStart, depsStart as AppPluginDependencies, params); + }, + }); } public start() {} diff --git a/examples/state_containers_examples/public/app.tsx b/examples/state_containers_examples/public/todo/app.tsx similarity index 100% rename from examples/state_containers_examples/public/app.tsx rename to examples/state_containers_examples/public/todo/app.tsx diff --git a/examples/state_containers_examples/public/todo.tsx b/examples/state_containers_examples/public/todo/todo.tsx similarity index 98% rename from examples/state_containers_examples/public/todo.tsx rename to examples/state_containers_examples/public/todo/todo.tsx index 84f64f99d01798..c0617620bde53f 100644 --- a/examples/state_containers_examples/public/todo.tsx +++ b/examples/state_containers_examples/public/todo/todo.tsx @@ -42,14 +42,14 @@ import { syncStates, getStateFromKbnUrl, BaseState, -} from '../../../src/plugins/kibana_utils/public'; -import { useUrlTracker } from '../../../src/plugins/kibana_react/public'; +} from '../../../../src/plugins/kibana_utils/public'; +import { useUrlTracker } from '../../../../src/plugins/kibana_react/public'; import { defaultState, pureTransitions, TodoActions, TodoState, -} from '../../../src/plugins/kibana_utils/demos/state_containers/todomvc'; +} from '../../../../src/plugins/kibana_utils/demos/state_containers/todomvc'; interface GlobalState { text: string; diff --git a/examples/state_containers_examples/public/with_data_services/application.tsx b/examples/state_containers_examples/public/with_data_services/application.tsx new file mode 100644 index 00000000000000..1de3cbbc5f9884 --- /dev/null +++ b/examples/state_containers_examples/public/with_data_services/application.tsx @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { createBrowserHistory } from 'history'; +import { AppMountParameters, CoreStart } from '../../../../src/core/public'; +import { AppPluginDependencies } from './types'; +import { StateDemoApp } from './components/app'; +import { createKbnUrlStateStorage } from '../../../../src/plugins/kibana_utils/public/'; + +export const renderApp = ( + { notifications, http }: CoreStart, + { navigation, data }: AppPluginDependencies, + { appBasePath, element }: AppMountParameters +) => { + const history = createBrowserHistory({ basename: appBasePath }); + const kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history }); + + ReactDOM.render( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/state_containers_examples/public/with_data_services/components/app.tsx b/examples/state_containers_examples/public/with_data_services/components/app.tsx new file mode 100644 index 00000000000000..c820929d8a61da --- /dev/null +++ b/examples/state_containers_examples/public/with_data_services/components/app.tsx @@ -0,0 +1,243 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useRef, useState, useCallback } from 'react'; +import { History } from 'history'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { Router } from 'react-router-dom'; + +import { + EuiFieldText, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageHeader, + EuiTitle, +} from '@elastic/eui'; + +import { CoreStart } from '../../../../../src/core/public'; +import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; +import { + connectToQueryState, + syncQueryStateWithUrl, + DataPublicPluginStart, + IIndexPattern, + QueryState, + Filter, + esFilters, + Query, +} from '../../../../../src/plugins/data/public'; +import { + BaseState, + BaseStateContainer, + createStateContainer, + createStateContainerReactHelpers, + IKbnUrlStateStorage, + ReduxLikeStateContainer, + syncState, +} from '../../../../../src/plugins/kibana_utils/public'; +import { PLUGIN_ID, PLUGIN_NAME } from '../../../common'; + +interface StateDemoAppDeps { + notifications: CoreStart['notifications']; + http: CoreStart['http']; + navigation: NavigationPublicPluginStart; + data: DataPublicPluginStart; + history: History; + kbnUrlStateStorage: IKbnUrlStateStorage; +} + +interface AppState { + name: string; + filters: Filter[]; + query?: Query; +} +const defaultAppState: AppState = { + name: '', + filters: [], +}; +const { + Provider: AppStateContainerProvider, + useState: useAppState, + useContainer: useAppStateContainer, +} = createStateContainerReactHelpers>(); + +const App = ({ + notifications, + http, + navigation, + data, + history, + kbnUrlStateStorage, +}: StateDemoAppDeps) => { + const appStateContainer = useAppStateContainer(); + const appState = useAppState(); + + useGlobalStateSyncing(data.query, kbnUrlStateStorage); + useAppStateSyncing(appStateContainer, data.query, kbnUrlStateStorage); + + const onQuerySubmit = useCallback( + ({ query }) => { + appStateContainer.set({ ...appState, query }); + }, + [appStateContainer, appState] + ); + + const indexPattern = useIndexPattern(data); + if (!indexPattern) + return
No index pattern found. Please create an intex patter before loading...
; + + // Render the application DOM. + // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. + return ( + + + <> + + + + + +

+ +

+
+
+ + appStateContainer.set({ ...appState, name: e.target.value })} + aria-label="My name" + /> + +
+
+ +
+
+ ); +}; + +export const StateDemoApp = (props: StateDemoAppDeps) => { + const appStateContainer = useCreateStateContainer(defaultAppState); + + return ( + + + + ); +}; + +function useCreateStateContainer( + defaultState: State +): ReduxLikeStateContainer { + const stateContainerRef = useRef | null>(null); + if (!stateContainerRef.current) { + stateContainerRef.current = createStateContainer(defaultState); + } + return stateContainerRef.current; +} + +function useIndexPattern(data: DataPublicPluginStart) { + const [indexPattern, setIndexPattern] = useState(); + useEffect(() => { + const fetchIndexPattern = async () => { + const defaultIndexPattern = await data.indexPatterns.getDefault(); + if (defaultIndexPattern) { + setIndexPattern(defaultIndexPattern); + } + }; + fetchIndexPattern(); + }, [data.indexPatterns]); + + return indexPattern; +} + +function useGlobalStateSyncing( + query: DataPublicPluginStart['query'], + kbnUrlStateStorage: IKbnUrlStateStorage +) { + // setup sync state utils + useEffect(() => { + // sync global filters, time filters, refresh interval from data.query to url '_g' + const { stop } = syncQueryStateWithUrl(query, kbnUrlStateStorage); + return () => { + stop(); + }; + }, [query, kbnUrlStateStorage]); +} + +function useAppStateSyncing( + appStateContainer: BaseStateContainer, + query: DataPublicPluginStart['query'], + kbnUrlStateStorage: IKbnUrlStateStorage +) { + // setup sync state utils + useEffect(() => { + // sync app filters with app state container from data.query to state container + const stopSyncingQueryAppStateWithStateContainer = connectToQueryState( + query, + appStateContainer, + { filters: esFilters.FilterStateStore.APP_STATE } + ); + + // sets up syncing app state container with url + const { start: startSyncingAppStateWithUrl, stop: stopSyncingAppStateWithUrl } = syncState({ + storageKey: '_a', + stateStorage: kbnUrlStateStorage, + stateContainer: { + ...appStateContainer, + // stateSync utils requires explicit handling of default state ("null") + set: state => state && appStateContainer.set(state), + }, + }); + + // merge initial state from app state container and current state in url + const initialAppState: AppState = { + ...appStateContainer.get(), + ...kbnUrlStateStorage.get('_a'), + }; + // trigger state update. actually needed in case some data was in url + appStateContainer.set(initialAppState); + + // set current url to whatever is in app state container + kbnUrlStateStorage.set('_a', initialAppState); + + // finally start syncing state containers with url + startSyncingAppStateWithUrl(); + + return () => { + stopSyncingQueryAppStateWithStateContainer(); + stopSyncingAppStateWithUrl(); + }; + }, [query, kbnUrlStateStorage, appStateContainer]); +} diff --git a/examples/state_containers_examples/public/with_data_services/types.ts b/examples/state_containers_examples/public/with_data_services/types.ts new file mode 100644 index 00000000000000..c63074a7a3810e --- /dev/null +++ b/examples/state_containers_examples/public/with_data_services/types.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface StateDemoPublicPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface StateDemoPublicPluginStart {} + +export interface AppPluginDependencies { + data: DataPublicPluginStart; + navigation: NavigationPublicPluginStart; +} diff --git a/examples/state_containers_examples/server/index.ts b/examples/state_containers_examples/server/index.ts new file mode 100644 index 00000000000000..51005d78462a26 --- /dev/null +++ b/examples/state_containers_examples/server/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../src/core/server'; +import { StateDemoServerPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new StateDemoServerPlugin(initializerContext); +} + +export { StateDemoServerPlugin as Plugin }; +export * from '../common'; diff --git a/examples/state_containers_examples/server/plugin.ts b/examples/state_containers_examples/server/plugin.ts new file mode 100644 index 00000000000000..1c3fa9bfb290e9 --- /dev/null +++ b/examples/state_containers_examples/server/plugin.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../src/core/server'; + +import { StateDemoPluginSetup, StateDemoPluginStart } from './types'; +import { defineRoutes } from './routes'; + +export class StateDemoServerPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('State_demo: Ssetup'); + const router = core.http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('State_demo: Started'); + return {}; + } + + public stop() {} +} + +export { StateDemoServerPlugin as Plugin }; diff --git a/examples/state_containers_examples/server/routes/index.ts b/examples/state_containers_examples/server/routes/index.ts new file mode 100644 index 00000000000000..f6da48ae62c617 --- /dev/null +++ b/examples/state_containers_examples/server/routes/index.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRouter } from '../../../../src/core/server'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: '/api/state_demo/example', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); +} diff --git a/examples/state_containers_examples/server/types.ts b/examples/state_containers_examples/server/types.ts new file mode 100644 index 00000000000000..6acfc27bd681b5 --- /dev/null +++ b/examples/state_containers_examples/server/types.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface StateDemoPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface StateDemoPluginStart {} diff --git a/examples/state_containers_examples/tsconfig.json b/examples/state_containers_examples/tsconfig.json index 091130487791bd..3f43072c2aade0 100644 --- a/examples/state_containers_examples/tsconfig.json +++ b/examples/state_containers_examples/tsconfig.json @@ -9,6 +9,7 @@ "public/**/*.ts", "public/**/*.tsx", "server/**/*.ts", + "common/**/*.ts", "../../typings/**/*" ], "exclude": [] diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 075516d52bab63..84dd73882d1343 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -31,18 +31,18 @@ import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_emp import { migrateLegacyQuery, subscribeWithScope } from '../legacy_imports'; import { + connectToQueryState, esFilters, IndexPattern, IndexPatternsContract, Query, SavedQuery, - syncAppFilters, - syncQuery, + syncQueryStateWithUrl, } from '../../../../../../plugins/data/public'; import { + getSavedObjectFinder, SaveResult, showSaveModal, - getSavedObjectFinder, } from '../../../../../../plugins/saved_objects/public'; import { @@ -129,9 +129,9 @@ export class DashboardAppController { // starts syncing `_g` portion of url with query services // note: dashboard_state_manager.ts syncs `_a` portion of url const { - stop: stopSyncingGlobalStateWithUrl, + stop: stopSyncingQueryServiceStateWithUrl, hasInheritedQueryFromUrl: hasInheritedGlobalStateFromUrl, - } = syncQuery(queryService, kbnUrlStateStorage); + } = syncQueryStateWithUrl(queryService, kbnUrlStateStorage); let lastReloadRequestTime = 0; @@ -148,11 +148,20 @@ export class DashboardAppController { history, }); - const stopSyncingAppFilters = syncAppFilters(filterManager, { - set: filters => dashboardStateManager.setFilters(filters), - get: () => dashboardStateManager.appState.filters, - state$: dashboardStateManager.appState$.pipe(map(state => state.filters)), - }); + // sync initial app filters from state to filterManager + filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters)); + // setup syncing of app filters between appState and filterManager + const stopSyncingAppFilters = connectToQueryState( + queryService, + { + set: ({ filters }) => dashboardStateManager.setFilters(filters || []), + get: () => ({ filters: dashboardStateManager.appState.filters }), + state$: dashboardStateManager.appState$.pipe(map(state => ({ filters: state.filters }))), + }, + { + filters: esFilters.FilterStateStore.APP_STATE, + } + ); // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. @@ -899,7 +908,7 @@ export class DashboardAppController { $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); - stopSyncingGlobalStateWithUrl(); + stopSyncingQueryServiceStateWithUrl(); stopSyncingAppFilters(); visibleSubscription.unsubscribe(); $scope.timefilterSubscriptions$.unsubscribe(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js index ce9cc85be57b27..35b510894179de 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js @@ -33,7 +33,7 @@ import { } from '../../../../../../plugins/kibana_utils/public'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; -import { syncQuery } from '../../../../../../plugins/data/public'; +import { syncQueryStateWithUrl } from '../../../../../../plugins/data/public'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -98,7 +98,7 @@ export function initDashboardApp(app, deps) { const dashboardConfig = deps.dashboardConfig; // syncs `_g` portion of url with query services - const { stop: stopSyncingGlobalStateWithUrl } = syncQuery( + const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( deps.data.query, kbnUrlStateStorage ); @@ -132,7 +132,7 @@ export function initDashboardApp(app, deps) { $scope.core = deps.core; $scope.$on('$destroy', () => { - stopSyncingGlobalStateWithUrl(); + stopSyncingQueryServiceStateWithUrl(); }); }, resolve: { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 7d64ee969212f4..9e645b7fb3c5f4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -18,6 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { App, AppMountParameters, @@ -29,7 +30,11 @@ import { } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { RenderDeps } from './np_ready/application'; -import { DataPublicPluginStart, DataPublicPluginSetup } from '../../../../../plugins/data/public'; +import { + DataPublicPluginStart, + DataPublicPluginSetup, + esFilters, +} from '../../../../../plugins/data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; @@ -46,7 +51,6 @@ import { } from '../../../../../plugins/kibana_legacy/public'; import { createSavedDashboardLoader } from './saved_dashboard/saved_dashboards'; import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public'; -import { getQueryStateContainer } from '../../../../../plugins/data/public'; export interface DashboardPluginStartDependencies { data: DataPublicPluginStart; @@ -78,9 +82,6 @@ export class DashboardPlugin implements Plugin { constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, { home, kibanaLegacy, data }: DashboardPluginSetupDependencies) { - const { querySyncStateContainer, stop: stopQuerySyncStateContainer } = getQueryStateContainer( - data.query - ); const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: `#${DashboardConstants.LANDING_PAGE_PATH}`, @@ -97,12 +98,19 @@ export class DashboardPlugin implements Plugin { stateParams: [ { kbnUrlKey: '_g', - stateUpdate$: querySyncStateContainer.state$, + stateUpdate$: data.query.state$.pipe( + filter( + ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(esFilters.isFilterPinned), + })) + ), }, ], }); this.stopUrlTracking = () => { - stopQuerySyncStateContainer(); stopUrlTracker(); }; const app: App = { diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index e8ded9d99f8924..3ba0418d35f718 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -18,6 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { AppMountParameters, CoreSetup, CoreStart, Plugin } from 'kibana/public'; import angular, { auto } from 'angular'; @@ -25,7 +26,7 @@ import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; import { DataPublicPluginStart, DataPublicPluginSetup, - getQueryStateContainer, + esFilters, } from '../../../../../plugins/data/public'; import { registerFeature } from './np_ready/register_feature'; import './kibana_services'; @@ -103,9 +104,6 @@ export class DiscoverPlugin implements Plugin { public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>; setup(core: CoreSetup, plugins: DiscoverSetupPlugins): DiscoverSetup { - const { querySyncStateContainer, stop: stopQuerySyncStateContainer } = getQueryStateContainer( - plugins.data.query - ); const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: '#/discover', @@ -115,12 +113,19 @@ export class DiscoverPlugin implements Plugin { stateParams: [ { kbnUrlKey: '_g', - stateUpdate$: querySyncStateContainer.state$, + stateUpdate$: plugins.data.query.state$.pipe( + filter( + ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(esFilters.isFilterPinned), + })) + ), }, ], }); this.stopUrlTracking = () => { - stopQuerySyncStateContainer(); stopUrlTracker(); }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 9f2283d29c203c..b9e4487ae84fbc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -19,6 +19,7 @@ import { BehaviorSubject } from 'rxjs'; import { i18n } from '@kbn/i18n'; +import { filter, map } from 'rxjs/operators'; import { AppMountParameters, @@ -33,7 +34,7 @@ import { Storage, createKbnUrlTracker } from '../../../../../plugins/kibana_util import { DataPublicPluginStart, DataPublicPluginSetup, - getQueryStateContainer, + esFilters, } from '../../../../../plugins/data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; @@ -85,9 +86,6 @@ export class VisualizePlugin implements Plugin { core: CoreSetup, { home, kibanaLegacy, usageCollection, data }: VisualizePluginSetupDependencies ) { - const { querySyncStateContainer, stop: stopQuerySyncStateContainer } = getQueryStateContainer( - data.query - ); const { appMounted, appUnMounted, stop: stopUrlTracker, setActiveUrl } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: '#/visualize', @@ -97,12 +95,19 @@ export class VisualizePlugin implements Plugin { stateParams: [ { kbnUrlKey: '_g', - stateUpdate$: querySyncStateContainer.state$, + stateUpdate$: data.query.state$.pipe( + filter( + ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(esFilters.isFilterPinned), + })) + ), }, ], }); this.stopUrlTracking = () => { - stopQuerySyncStateContainer(); stopUrlTracker(); }; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index cf8537ba7ab3ec..75f48beb140a2c 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -104,6 +104,7 @@ export const npSetup = { getProvider: sinon.fake(), }, query: { + state$: mockObservable(), filterManager: { getFetches$: sinon.fake(), getFilters: sinon.fake(), diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 978f140eb1d263..5dcf51ecc81eb6 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -294,11 +294,11 @@ export { Filter, Query, RefreshInterval, TimeRange } from '../common'; export { createSavedQueryService, - syncAppFilters, - syncQuery, + connectToQueryState, + syncQueryStateWithUrl, + QueryState, getTime, getQueryLog, - getQueryStateContainer, FilterManager, SavedQuery, SavedQueryService, diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index 2710dadaa23a33..47b0a5b871ce2e 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -17,7 +17,8 @@ * under the License. */ -import { QueryService, QuerySetup } from '.'; +import { Observable } from 'rxjs'; +import { QueryService, QuerySetup, QueryStart } from '.'; import { timefilterServiceMock } from './timefilter/timefilter_service.mock'; type QueryServiceClientContract = PublicMethodsOf; @@ -26,16 +27,18 @@ const createSetupContractMock = () => { const setupContract: jest.Mocked = { filterManager: jest.fn() as any, timefilter: timefilterServiceMock.createSetupContract(), + state$: new Observable(), }; return setupContract; }; const createStartContractMock = () => { - const startContract = { + const startContract: jest.Mocked = { filterManager: jest.fn() as any, timefilter: timefilterServiceMock.createStartContract(), savedQueries: jest.fn() as any, + state$: new Observable(), }; return startContract; diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index ebef8b8d450500..c885d596f19435 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -17,11 +17,13 @@ * under the License. */ +import { share } from 'rxjs/operators'; import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { FilterManager } from './filter_manager'; import { TimefilterService, TimefilterSetup } from './timefilter'; import { createSavedQueryService } from './saved_query/saved_query_service'; +import { createQueryStateObservable } from './state_sync/create_global_query_observable'; /** * Query Service @@ -36,6 +38,8 @@ export class QueryService { filterManager!: FilterManager; timefilter!: TimefilterSetup; + state$!: ReturnType; + public setup({ uiSettings, storage }: QueryServiceDependencies) { this.filterManager = new FilterManager(uiSettings); @@ -45,9 +49,15 @@ export class QueryService { storage, }); + this.state$ = createQueryStateObservable({ + filterManager: this.filterManager, + timefilter: this.timefilter, + }).pipe(share()); + return { filterManager: this.filterManager, timefilter: this.timefilter, + state$: this.state$, }; } @@ -55,6 +65,7 @@ export class QueryService { return { filterManager: this.filterManager, timefilter: this.timefilter, + state$: this.state$, savedQueries: createSavedQueryService(savedObjects.client), }; } diff --git a/src/plugins/data/public/query/state_sync/README.md b/src/plugins/data/public/query/state_sync/README.md new file mode 100644 index 00000000000000..6b9b158100573c --- /dev/null +++ b/src/plugins/data/public/query/state_sync/README.md @@ -0,0 +1,3 @@ +# Query state syncing utilities + +Set of helpers to connect data services to state containers and state syncing utilities diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts new file mode 100644 index 00000000000000..5da929c441cdee --- /dev/null +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts @@ -0,0 +1,465 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Subscription } from 'rxjs'; +import { FilterManager } from '../filter_manager'; +import { getFilter } from '../filter_manager/test_helpers/get_stub_filter'; +import { Filter, FilterStateStore } from '../../../common'; +import { coreMock } from '../../../../../core/public/mocks'; +import { BaseStateContainer, createStateContainer, Storage } from '../../../../kibana_utils/public'; +import { QueryService, QueryStart } from '../query_service'; +import { StubBrowserStorage } from '../../../../../test_utils/public/stub_browser_storage'; +import { connectToQueryState } from './connect_to_query_state'; +import { TimefilterContract } from '../timefilter'; +import { QueryState } from './types'; + +const connectToQueryGlobalState = (query: QueryStart, state: BaseStateContainer) => + connectToQueryState(query, state, { + refreshInterval: true, + time: true, + filters: FilterStateStore.GLOBAL_STATE, + }); + +const connectToQueryAppState = (query: QueryStart, state: BaseStateContainer) => + connectToQueryState(query, state, { + filters: FilterStateStore.APP_STATE, + }); + +const setupMock = coreMock.createSetup(); +const startMock = coreMock.createStart(); + +setupMock.uiSettings.get.mockImplementation((key: string) => { + switch (key) { + case 'filters:pinnedByDefault': + return true; + case 'timepicker:timeDefaults': + return { from: 'now-15m', to: 'now' }; + case 'timepicker:refreshIntervalDefaults': + return { pause: false, value: 0 }; + default: + throw new Error(`sync_query test: not mocked uiSetting: ${key}`); + } +}); + +describe('connect_to_global_state', () => { + let queryServiceStart: QueryStart; + let filterManager: FilterManager; + let timeFilter: TimefilterContract; + let globalState: BaseStateContainer; + let globalStateSub: Subscription; + let globalStateChangeTriggered = jest.fn(); + let filterManagerChangeSub: Subscription; + let filterManagerChangeTriggered = jest.fn(); + + let gF1: Filter; + let gF2: Filter; + let aF1: Filter; + let aF2: Filter; + + beforeEach(() => { + const queryService = new QueryService(); + queryService.setup({ + uiSettings: setupMock.uiSettings, + storage: new Storage(new StubBrowserStorage()), + }); + queryServiceStart = queryService.start(startMock.savedObjects); + filterManager = queryServiceStart.filterManager; + timeFilter = queryServiceStart.timefilter.timefilter; + + globalState = createStateContainer({}); + globalStateChangeTriggered = jest.fn(); + globalStateSub = globalState.state$.subscribe(globalStateChangeTriggered); + + filterManagerChangeTriggered = jest.fn(); + filterManagerChangeSub = filterManager.getUpdates$().subscribe(filterManagerChangeTriggered); + + gF1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1'); + gF2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'key2', 'value2'); + aF1 = getFilter(FilterStateStore.APP_STATE, true, true, 'key3', 'value3'); + aF2 = getFilter(FilterStateStore.APP_STATE, false, false, 'key4', 'value4'); + }); + afterEach(() => { + globalStateSub.unsubscribe(); + filterManagerChangeSub.unsubscribe(); + }); + + test('state is initialized with state from query service', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + + expect(globalState.get()).toEqual({ + filters: filterManager.getGlobalFilters(), + refreshInterval: timeFilter.getRefreshInterval(), + time: timeFilter.getTime(), + }); + + stop(); + }); + + test('when time range changes, state container contains updated time range', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + timeFilter.setTime({ from: 'now-30m', to: 'now' }); + expect(globalState.get().time).toEqual({ + from: 'now-30m', + to: 'now', + }); + stop(); + }); + + test('when refresh interval changes, state container contains updated refresh interval', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + timeFilter.setRefreshInterval({ pause: true, value: 100 }); + expect(globalState.get().refreshInterval).toEqual({ + pause: true, + value: 100, + }); + stop(); + }); + + test('state changes should propagate to services', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalStateChangeTriggered.mockClear(); + globalState.set({ + ...globalState.get(), + filters: [gF1, gF2], + refreshInterval: { pause: true, value: 100 }, + time: { from: 'now-30m', to: 'now' }, + }); + + expect(globalStateChangeTriggered).toBeCalledTimes(1); + + expect(filterManager.getGlobalFilters()).toHaveLength(2); + expect(timeFilter.getRefreshInterval()).toEqual({ pause: true, value: 100 }); + expect(timeFilter.getTime()).toEqual({ from: 'now-30m', to: 'now' }); + stop(); + }); + + describe('sync from filterManager to global state', () => { + test('should sync global filters to global state when new global filters set to filterManager', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + + filterManager.setFilters([gF1, aF1]); + + expect(globalState.get().filters).toHaveLength(1); + stop(); + }); + + test('should not sync app filters to global state ', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + + filterManager.setFilters([aF1, aF2]); + + expect(globalState.get().filters).toHaveLength(0); + stop(); + }); + + test("should not trigger changes when global filters didn't change", () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalStateChangeTriggered.mockClear(); + + filterManager.setFilters([gF1, aF1]); + filterManager.setFilters([gF1, aF2]); + + expect(globalStateChangeTriggered).toBeCalledTimes(1); + expect(globalState.get().filters).toHaveLength(1); + + stop(); + }); + + test('should trigger changes when global filters change', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalStateChangeTriggered.mockClear(); + + filterManager.setFilters([gF1, aF1]); + filterManager.setFilters([gF2, aF1]); + + expect(globalStateChangeTriggered).toBeCalledTimes(2); + expect(globalState.get().filters).toHaveLength(1); + + stop(); + }); + + test('resetting filters should sync to global state', () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + + filterManager.setFilters([gF1, aF1]); + + expect(globalState.get().filters).toHaveLength(1); + + filterManager.removeAll(); + + expect(globalState.get().filters).toHaveLength(0); + + stop(); + }); + + test("shouldn't sync filters when syncing is stopped", () => { + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + + filterManager.setFilters([gF1, aF1]); + + expect(globalState.get().filters).toHaveLength(1); + + stop(); + + filterManager.removeAll(); + + expect(globalState.get().filters).toHaveLength(1); + }); + + test('should pick up initial state from filterManager', () => { + globalState.set({ filters: [gF1] }); + filterManager.setFilters([aF1]); + + globalStateChangeTriggered.mockClear(); + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + expect(globalStateChangeTriggered).toBeCalledTimes(1); + expect(globalState.get().filters).toHaveLength(0); + + stop(); + }); + }); + describe('sync from global state to filterManager', () => { + test('changes to global state should be synced to global filters', () => { + filterManager.setFilters([aF1]); + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalStateChangeTriggered.mockClear(); + + globalState.set({ ...globalState.get(), filters: [gF1] }); + + expect(filterManager.getFilters()).toHaveLength(2); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(globalStateChangeTriggered).toBeCalledTimes(1); + stop(); + }); + + test('app filters should remain untouched', () => { + filterManager.setFilters([gF1, gF2, aF1, aF2]); + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalStateChangeTriggered.mockClear(); + + globalState.set({ ...globalState.get(), filters: [] }); + + expect(filterManager.getFilters()).toHaveLength(2); + expect(filterManager.getAppFilters()).toHaveLength(2); + expect(filterManager.getGlobalFilters()).toHaveLength(0); + expect(globalStateChangeTriggered).toBeCalledTimes(1); + stop(); + }); + + test("if filters are not changed, filterManager shouldn't trigger update", () => { + filterManager.setFilters([gF1, gF2, aF1, aF2]); + filterManagerChangeTriggered.mockClear(); + + globalState.set({ ...globalState.get(), filters: [gF1, gF2] }); + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalState.set({ ...globalState.get(), filters: [gF1, gF2] }); + + expect(filterManagerChangeTriggered).toBeCalledTimes(0); + stop(); + }); + + test('stop() should stop syncing', () => { + filterManager.setFilters([gF1, gF2, aF1, aF2]); + const stop = connectToQueryGlobalState(queryServiceStart, globalState); + globalState.set({ ...globalState.get(), filters: [] }); + expect(filterManager.getFilters()).toHaveLength(2); + stop(); + globalState.set({ ...globalState.get(), filters: [gF1] }); + expect(filterManager.getFilters()).toHaveLength(2); + }); + }); +}); + +describe('connect_to_app_state', () => { + let queryServiceStart: QueryStart; + let filterManager: FilterManager; + let appState: BaseStateContainer; + let appStateSub: Subscription; + let appStateChangeTriggered = jest.fn(); + let filterManagerChangeSub: Subscription; + let filterManagerChangeTriggered = jest.fn(); + + let gF1: Filter; + let gF2: Filter; + let aF1: Filter; + let aF2: Filter; + + beforeEach(() => { + const queryService = new QueryService(); + queryService.setup({ + uiSettings: setupMock.uiSettings, + storage: new Storage(new StubBrowserStorage()), + }); + queryServiceStart = queryService.start(startMock.savedObjects); + filterManager = queryServiceStart.filterManager; + + appState = createStateContainer({}); + appStateChangeTriggered = jest.fn(); + appStateSub = appState.state$.subscribe(appStateChangeTriggered); + + filterManagerChangeTriggered = jest.fn(); + filterManagerChangeSub = filterManager.getUpdates$().subscribe(filterManagerChangeTriggered); + + gF1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1'); + gF2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'key2', 'value2'); + aF1 = getFilter(FilterStateStore.APP_STATE, true, true, 'key3', 'value3'); + aF2 = getFilter(FilterStateStore.APP_STATE, false, false, 'key4', 'value4'); + }); + afterEach(() => { + appStateSub.unsubscribe(); + filterManagerChangeSub.unsubscribe(); + }); + + describe('sync from filterManager to app state', () => { + test('should sync app filters to app state when new app filters set to filterManager', () => { + const stop = connectToQueryAppState(queryServiceStart, appState); + + filterManager.setFilters([gF1, aF1]); + + expect(appState.get().filters).toHaveLength(1); + stop(); + }); + + test('should not sync global filters to app state ', () => { + const stop = connectToQueryAppState(queryServiceStart, appState); + + filterManager.setFilters([gF1, gF2]); + + expect(appState.get().filters).toHaveLength(0); + stop(); + }); + + test("should not trigger changes when app filters didn't change", () => { + const stop = connectToQueryAppState(queryServiceStart, appState); + appStateChangeTriggered.mockClear(); + + filterManager.setFilters([gF1, aF1]); + filterManager.setFilters([gF2, aF1]); + + expect(appStateChangeTriggered).toBeCalledTimes(1); + expect(appState.get().filters).toHaveLength(1); + + stop(); + }); + + test('should trigger changes when app filters change', () => { + const stop = connectToQueryAppState(queryServiceStart, appState); + appStateChangeTriggered.mockClear(); + + filterManager.setFilters([gF1, aF1]); + filterManager.setFilters([gF1, aF2]); + + expect(appStateChangeTriggered).toBeCalledTimes(2); + expect(appState.get().filters).toHaveLength(1); + + stop(); + }); + + test('resetting filters should sync to app state', () => { + const stop = connectToQueryAppState(queryServiceStart, appState); + + filterManager.setFilters([gF1, aF1]); + + expect(appState.get().filters).toHaveLength(1); + + filterManager.removeAll(); + + expect(appState.get().filters).toHaveLength(0); + + stop(); + }); + + test("shouldn't sync filters when syncing is stopped", () => { + const stop = connectToQueryAppState(queryServiceStart, appState); + + filterManager.setFilters([gF1, aF1]); + + expect(appState.get().filters).toHaveLength(1); + + stop(); + + filterManager.removeAll(); + + expect(appState.get().filters).toHaveLength(1); + }); + + test('should pick up initial state from filterManager', () => { + appState.set({ filters: [aF1] }); + filterManager.setFilters([gF1]); + + appStateChangeTriggered.mockClear(); + const stop = connectToQueryAppState(queryServiceStart, appState); + expect(appStateChangeTriggered).toBeCalledTimes(1); + expect(appState.get().filters).toHaveLength(0); + + stop(); + }); + }); + describe('sync from app state to filterManager', () => { + test('changes to app state should be synced to app filters', () => { + filterManager.setFilters([gF1]); + const stop = connectToQueryAppState(queryServiceStart, appState); + appStateChangeTriggered.mockClear(); + + appState.set({ filters: [aF1] }); + + expect(filterManager.getFilters()).toHaveLength(2); + expect(filterManager.getAppFilters()).toHaveLength(1); + expect(filterManager.getGlobalFilters()).toHaveLength(1); + expect(appStateChangeTriggered).toBeCalledTimes(1); + stop(); + }); + + test('global filters should remain untouched', () => { + filterManager.setFilters([gF1, gF2, aF1, aF2]); + const stop = connectToQueryAppState(queryServiceStart, appState); + appStateChangeTriggered.mockClear(); + + appState.set({ filters: [] }); + + expect(filterManager.getFilters()).toHaveLength(2); + expect(filterManager.getGlobalFilters()).toHaveLength(2); + expect(appStateChangeTriggered).toBeCalledTimes(1); + stop(); + }); + + test("if filters are not changed, filterManager shouldn't trigger update", () => { + filterManager.setFilters([gF1, gF2, aF1, aF2]); + filterManagerChangeTriggered.mockClear(); + + appState.set({ filters: [aF1, aF2] }); + const stop = connectToQueryAppState(queryServiceStart, appState); + appState.set({ filters: [aF1, aF2] }); + + expect(filterManagerChangeTriggered).toBeCalledTimes(0); + stop(); + }); + + test('stop() should stop syncing', () => { + filterManager.setFilters([gF1, gF2, aF1, aF2]); + const stop = connectToQueryAppState(queryServiceStart, appState); + appState.set({ filters: [] }); + expect(filterManager.getFilters()).toHaveLength(2); + stop(); + appState.set({ filters: [aF1] }); + expect(filterManager.getFilters()).toHaveLength(2); + }); + }); +}); diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts new file mode 100644 index 00000000000000..a22e66860c7658 --- /dev/null +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.ts @@ -0,0 +1,194 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Subscription } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import _ from 'lodash'; +import { BaseStateContainer } from '../../../../kibana_utils/public'; +import { COMPARE_ALL_OPTIONS, compareFilters } from '../filter_manager/lib/compare_filters'; +import { QuerySetup, QueryStart } from '../query_service'; +import { QueryState, QueryStateChange } from './types'; +import { FilterStateStore } from '../../../common/es_query/filters'; + +/** + * Helper to setup two-way syncing of global data and a state container + * @param QueryService: either setup or start + * @param stateContainer to use for syncing + */ +export const connectToQueryState = ( + { + timefilter: { timefilter }, + filterManager, + state$, + }: Pick, + stateContainer: BaseStateContainer, + syncConfig: { time?: boolean; refreshInterval?: boolean; filters?: FilterStateStore | boolean } +) => { + const syncKeys: Array = []; + if (syncConfig.time) { + syncKeys.push('time'); + } + if (syncConfig.refreshInterval) { + syncKeys.push('refreshInterval'); + } + if (syncConfig.filters) { + switch (syncConfig.filters) { + case true: + syncKeys.push('filters'); + break; + case FilterStateStore.APP_STATE: + syncKeys.push('appFilters'); + break; + case FilterStateStore.GLOBAL_STATE: + syncKeys.push('globalFilters'); + break; + } + } + + // initial syncing + // TODO: + // data services take precedence, this seems like a good default, + // and apps could anyway set their own value after initialisation, + // but maybe maybe this should be a configurable option? + const initialState: QueryState = { ...stateContainer.get() }; + let initialDirty = false; + if (syncConfig.time && !_.isEqual(initialState.time, timefilter.getTime())) { + initialState.time = timefilter.getTime(); + initialDirty = true; + } + if ( + syncConfig.refreshInterval && + !_.isEqual(initialState.refreshInterval, timefilter.getRefreshInterval()) + ) { + initialState.refreshInterval = timefilter.getRefreshInterval(); + initialDirty = true; + } + + if (syncConfig.filters) { + if (syncConfig.filters === true) { + if ( + !initialState.filters || + !compareFilters(initialState.filters, filterManager.getFilters(), COMPARE_ALL_OPTIONS) + ) { + initialState.filters = filterManager.getFilters(); + initialDirty = true; + } + } else if (syncConfig.filters === FilterStateStore.GLOBAL_STATE) { + if ( + !initialState.filters || + !compareFilters(initialState.filters, filterManager.getGlobalFilters(), COMPARE_ALL_OPTIONS) + ) { + initialState.filters = filterManager.getGlobalFilters(); + initialDirty = true; + } + } else if (syncConfig.filters === FilterStateStore.APP_STATE) { + if ( + !initialState.filters || + !compareFilters(initialState.filters, filterManager.getAppFilters(), COMPARE_ALL_OPTIONS) + ) { + initialState.filters = filterManager.getAppFilters(); + initialDirty = true; + } + } + } + + if (initialDirty) { + stateContainer.set({ ...stateContainer.get(), ...initialState }); + } + + // to ignore own state updates + let updateInProgress = false; + + const subs: Subscription[] = [ + state$ + .pipe( + filter(({ changes, state }) => { + if (updateInProgress) return false; + return syncKeys.some(syncKey => changes[syncKey]); + }), + map(({ changes }) => { + const newState: QueryState = {}; + if (syncConfig.time && changes.time) { + newState.time = timefilter.getTime(); + } + if (syncConfig.refreshInterval && changes.refreshInterval) { + newState.refreshInterval = timefilter.getRefreshInterval(); + } + if (syncConfig.filters) { + if (syncConfig.filters === true && changes.filters) { + newState.filters = filterManager.getFilters(); + } else if ( + syncConfig.filters === FilterStateStore.GLOBAL_STATE && + changes.globalFilters + ) { + newState.filters = filterManager.getGlobalFilters(); + } else if (syncConfig.filters === FilterStateStore.APP_STATE && changes.appFilters) { + newState.filters = filterManager.getAppFilters(); + } + } + return newState; + }) + ) + .subscribe(newState => { + stateContainer.set({ ...stateContainer.get(), ...newState }); + }), + stateContainer.state$.subscribe(state => { + updateInProgress = true; + + // cloneDeep is required because services are mutating passed objects + // and state in state container is frozen + if (syncConfig.time) { + const time = state.time || timefilter.getTimeDefaults(); + if (!_.isEqual(time, timefilter.getTime())) { + timefilter.setTime(_.cloneDeep(time)); + } + } + + if (syncConfig.refreshInterval) { + const refreshInterval = state.refreshInterval || timefilter.getRefreshIntervalDefaults(); + if (!_.isEqual(refreshInterval, timefilter.getRefreshInterval())) { + timefilter.setRefreshInterval(_.cloneDeep(refreshInterval)); + } + } + + if (syncConfig.filters) { + const filters = state.filters || []; + if (syncConfig.filters === true) { + if (!compareFilters(filters, filterManager.getFilters(), COMPARE_ALL_OPTIONS)) { + filterManager.setFilters(_.cloneDeep(filters)); + } + } else if (syncConfig.filters === FilterStateStore.APP_STATE) { + if (!compareFilters(filters, filterManager.getAppFilters(), COMPARE_ALL_OPTIONS)) { + filterManager.setAppFilters(_.cloneDeep(filters)); + } + } else if (syncConfig.filters === FilterStateStore.GLOBAL_STATE) { + if (!compareFilters(filters, filterManager.getGlobalFilters(), COMPARE_ALL_OPTIONS)) { + filterManager.setGlobalFilters(_.cloneDeep(filters)); + } + } + } + + updateInProgress = false; + }), + ]; + + return () => { + subs.forEach(s => s.unsubscribe()); + }; +}; diff --git a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts new file mode 100644 index 00000000000000..d0d97bfaaeb362 --- /dev/null +++ b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable, Subscription } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; +import { TimefilterSetup } from '../timefilter'; +import { COMPARE_ALL_OPTIONS, compareFilters, FilterManager } from '../filter_manager'; +import { QueryState, QueryStateChange } from './index'; +import { createStateContainer } from '../../../../kibana_utils/public'; +import { isFilterPinned } from '../../../common/es_query/filters'; + +export function createQueryStateObservable({ + timefilter: { timefilter }, + filterManager, +}: { + timefilter: TimefilterSetup; + filterManager: FilterManager; +}): Observable<{ changes: QueryStateChange; state: QueryState }> { + return new Observable(subscriber => { + const state = createStateContainer({ + time: timefilter.getTime(), + refreshInterval: timefilter.getRefreshInterval(), + filters: filterManager.getFilters(), + }); + + let currentChange: QueryStateChange = {}; + const subs: Subscription[] = [ + timefilter.getTimeUpdate$().subscribe(() => { + currentChange.time = true; + state.set({ ...state.get(), time: timefilter.getTime() }); + }), + timefilter.getRefreshIntervalUpdate$().subscribe(() => { + currentChange.refreshInterval = true; + state.set({ ...state.get(), refreshInterval: timefilter.getRefreshInterval() }); + }), + filterManager.getUpdates$().subscribe(() => { + currentChange.filters = true; + + const { filters } = state.get(); + const globalOld = filters?.filter(f => isFilterPinned(f)); + const appOld = filters?.filter(f => !isFilterPinned(f)); + const globalNew = filterManager.getGlobalFilters(); + const appNew = filterManager.getAppFilters(); + + if (!globalOld || !compareFilters(globalOld, globalNew, COMPARE_ALL_OPTIONS)) { + currentChange.globalFilters = true; + } + + if (!appOld || !compareFilters(appOld, appNew, COMPARE_ALL_OPTIONS)) { + currentChange.appFilters = true; + } + + state.set({ + ...state.get(), + filters: filterManager.getFilters(), + }); + }), + state.state$ + .pipe( + map(newState => ({ state: newState, changes: currentChange })), + tap(() => { + currentChange = {}; + }) + ) + .subscribe(subscriber), + ]; + return () => { + subs.forEach(s => s.unsubscribe()); + }; + }); +} diff --git a/src/plugins/data/public/query/state_sync/index.ts b/src/plugins/data/public/query/state_sync/index.ts index 27e02940765cf7..e1a3561e022dbf 100644 --- a/src/plugins/data/public/query/state_sync/index.ts +++ b/src/plugins/data/public/query/state_sync/index.ts @@ -17,5 +17,6 @@ * under the License. */ -export { syncQuery, getQueryStateContainer } from './sync_query'; -export { syncAppFilters } from './sync_app_filters'; +export { connectToQueryState } from './connect_to_query_state'; +export { syncQueryStateWithUrl } from './sync_state_with_url'; +export { QueryState, QueryStateChange } from './types'; diff --git a/src/plugins/data/public/query/state_sync/sync_app_filters.test.ts b/src/plugins/data/public/query/state_sync/sync_app_filters.test.ts deleted file mode 100644 index e01547b1c0fd8c..00000000000000 --- a/src/plugins/data/public/query/state_sync/sync_app_filters.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Subscription } from 'rxjs'; -import { FilterManager } from '../filter_manager'; -import { getFilter } from '../filter_manager/test_helpers/get_stub_filter'; -import { Filter, FilterStateStore } from '../../../common'; -import { syncAppFilters } from './sync_app_filters'; -import { coreMock } from '../../../../../core/public/mocks'; -import { BaseStateContainer, createStateContainer } from '../../../../kibana_utils/public'; - -const setupMock = coreMock.createSetup(); - -setupMock.uiSettings.get.mockImplementation((key: string) => { - return true; -}); - -describe('sync_app_filters', () => { - let filterManager: FilterManager; - let appState: BaseStateContainer; - let appStateSub: Subscription; - let appStateChangeTriggered = jest.fn(); - let filterManagerChangeSub: Subscription; - let filterManagerChangeTriggered = jest.fn(); - - let gF1: Filter; - let gF2: Filter; - let aF1: Filter; - let aF2: Filter; - - beforeEach(() => { - filterManager = new FilterManager(setupMock.uiSettings); - appState = createStateContainer([] as Filter[]); - appStateChangeTriggered = jest.fn(); - appStateSub = appState.state$.subscribe(appStateChangeTriggered); - - filterManagerChangeTriggered = jest.fn(); - filterManagerChangeSub = filterManager.getUpdates$().subscribe(filterManagerChangeTriggered); - - gF1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1'); - gF2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'key2', 'value2'); - aF1 = getFilter(FilterStateStore.APP_STATE, true, true, 'key3', 'value3'); - aF2 = getFilter(FilterStateStore.APP_STATE, false, false, 'key4', 'value4'); - }); - afterEach(() => { - appStateSub.unsubscribe(); - filterManagerChangeSub.unsubscribe(); - }); - - describe('sync from filterManager to app state', () => { - test('should sync app filters to app state when new app filters set to filterManager', () => { - const stop = syncAppFilters(filterManager, appState); - - filterManager.setFilters([gF1, aF1]); - - expect(appState.get()).toHaveLength(1); - stop(); - }); - - test('should not sync global filters to app state ', () => { - const stop = syncAppFilters(filterManager, appState); - - filterManager.setFilters([gF1, gF2]); - - expect(appState.get()).toHaveLength(0); - stop(); - }); - - test("should not trigger changes when app filters didn't change", () => { - const stop = syncAppFilters(filterManager, appState); - - filterManager.setFilters([gF1, aF1]); - - filterManager.setFilters([gF2, aF1]); - - expect(appStateChangeTriggered).toBeCalledTimes(1); - expect(appState.get()).toHaveLength(1); - - stop(); - }); - - test('should trigger changes when app filters change', () => { - const stop = syncAppFilters(filterManager, appState); - - filterManager.setFilters([gF1, aF1]); - filterManager.setFilters([gF1, aF2]); - - expect(appStateChangeTriggered).toBeCalledTimes(2); - expect(appState.get()).toHaveLength(1); - - stop(); - }); - - test('resetting filters should sync to app state', () => { - const stop = syncAppFilters(filterManager, appState); - - filterManager.setFilters([gF1, aF1]); - - expect(appState.get()).toHaveLength(1); - - filterManager.removeAll(); - - expect(appState.get()).toHaveLength(0); - - stop(); - }); - - test("shouldn't sync filters when syncing is stopped", () => { - const stop = syncAppFilters(filterManager, appState); - - filterManager.setFilters([gF1, aF1]); - - expect(appState.get()).toHaveLength(1); - - stop(); - - filterManager.removeAll(); - - expect(appState.get()).toHaveLength(1); - }); - }); - describe('sync from app state to filterManager', () => { - test('should pick up initial state from app state', () => { - appState.set([aF1]); - filterManager.setFilters([gF1]); - - const stop = syncAppFilters(filterManager, appState); - expect(filterManager.getFilters()).toHaveLength(2); - expect(appStateChangeTriggered).toBeCalledTimes(1); - - stop(); - }); - - test('changes to app state should be synced to app filters', () => { - filterManager.setFilters([gF1]); - const stop = syncAppFilters(filterManager, appState); - - appState.set([aF1]); - - expect(filterManager.getFilters()).toHaveLength(2); - expect(filterManager.getAppFilters()).toHaveLength(1); - expect(filterManager.getGlobalFilters()).toHaveLength(1); - expect(appStateChangeTriggered).toBeCalledTimes(1); - stop(); - }); - - test('global filters should remain untouched', () => { - filterManager.setFilters([gF1, gF2, aF1, aF2]); - const stop = syncAppFilters(filterManager, appState); - - appState.set([]); - - expect(filterManager.getFilters()).toHaveLength(2); - expect(filterManager.getGlobalFilters()).toHaveLength(2); - expect(appStateChangeTriggered).toBeCalledTimes(1); - stop(); - }); - - test("if filters are not changed, filterManager shouldn't trigger update", () => { - filterManager.setFilters([gF1, gF2, aF1, aF2]); - filterManagerChangeTriggered.mockClear(); - - appState.set([aF1, aF2]); - const stop = syncAppFilters(filterManager, appState); - appState.set([aF1, aF2]); - - expect(filterManagerChangeTriggered).toBeCalledTimes(0); - stop(); - }); - - test('stop() should stop syncing', () => { - filterManager.setFilters([gF1, gF2, aF1, aF2]); - const stop = syncAppFilters(filterManager, appState); - appState.set([]); - expect(filterManager.getFilters()).toHaveLength(2); - stop(); - appState.set([aF1]); - expect(filterManager.getFilters()).toHaveLength(2); - }); - }); -}); diff --git a/src/plugins/data/public/query/state_sync/sync_app_filters.ts b/src/plugins/data/public/query/state_sync/sync_app_filters.ts deleted file mode 100644 index d9956fcc0f6ae5..00000000000000 --- a/src/plugins/data/public/query/state_sync/sync_app_filters.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { filter, map } from 'rxjs/operators'; -import { COMPARE_ALL_OPTIONS, compareFilters } from '../filter_manager/lib/compare_filters'; -import { Filter } from '../../../common'; -import { FilterManager } from '../filter_manager'; -import { BaseStateContainer } from '../../../../../plugins/kibana_utils/public'; - -/** - * Helper utility to sync application's state filters, with filter manager - * @param filterManager - * @param appState - */ -export function syncAppFilters( - filterManager: FilterManager, - appState: BaseStateContainer -) { - // make sure initial app filters are picked by filterManager - filterManager.setAppFilters(_.cloneDeep(appState.get())); - - const subs = [ - filterManager - .getUpdates$() - .pipe( - map(() => filterManager.getAppFilters()), - filter( - // continue only if app state filters updated - appFilters => !compareFilters(appFilters, appState.get(), COMPARE_ALL_OPTIONS) - ) - ) - .subscribe(appFilters => { - appState.set(appFilters); - }), - - // if appFilters in dashboardStateManager changed (e.g browser history update), - // sync it to filterManager - appState.state$.subscribe(() => { - if (!compareFilters(appState.get(), filterManager.getAppFilters(), COMPARE_ALL_OPTIONS)) { - filterManager.setAppFilters(_.cloneDeep(appState.get())); - } - }), - ]; - - return () => { - subs.forEach(s => s.unsubscribe()); - }; -} diff --git a/src/plugins/data/public/query/state_sync/sync_query.ts b/src/plugins/data/public/query/state_sync/sync_query.ts deleted file mode 100644 index 373f9aa0a56685..00000000000000 --- a/src/plugins/data/public/query/state_sync/sync_query.ts +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Subscription } from 'rxjs'; -import _ from 'lodash'; -import { filter, map } from 'rxjs/operators'; -import { - createStateContainer, - IKbnUrlStateStorage, - syncState, -} from '../../../../kibana_utils/public'; -import { COMPARE_ALL_OPTIONS, compareFilters } from '../filter_manager/lib/compare_filters'; -import { Filter, RefreshInterval, TimeRange } from '../../../common'; -import { QuerySetup, QueryStart } from '../query_service'; - -const GLOBAL_STATE_STORAGE_KEY = '_g'; - -export interface QuerySyncState { - time?: TimeRange; - refreshInterval?: RefreshInterval; - filters?: Filter[]; -} - -/** - * Helper utility to set up syncing between query services and url's '_g' query param - */ -export const syncQuery = (queryStart: QueryStart, urlStateStorage: IKbnUrlStateStorage) => { - const { - timefilter: { timefilter }, - filterManager, - } = queryStart; - // retrieve current state from `_g` url - const initialStateFromUrl = urlStateStorage.get(GLOBAL_STATE_STORAGE_KEY); - - // remember whether there were info in the URL - const hasInheritedQueryFromUrl = Boolean( - initialStateFromUrl && Object.keys(initialStateFromUrl).length - ); - - const { - querySyncStateContainer, - stop: stopPullQueryState, - initialState, - } = getQueryStateContainer(queryStart, initialStateFromUrl || {}); - - const pushQueryStateSubscription = querySyncStateContainer.state$.subscribe( - ({ time, filters: globalFilters, refreshInterval }) => { - // cloneDeep is required because services are mutating passed objects - // and state in state container is frozen - if (time && !_.isEqual(time, timefilter.getTime())) { - timefilter.setTime(_.cloneDeep(time)); - } - - if (refreshInterval && !_.isEqual(refreshInterval, timefilter.getRefreshInterval())) { - timefilter.setRefreshInterval(_.cloneDeep(refreshInterval)); - } - - if ( - globalFilters && - !compareFilters(globalFilters, filterManager.getGlobalFilters(), COMPARE_ALL_OPTIONS) - ) { - filterManager.setGlobalFilters(_.cloneDeep(globalFilters)); - } - } - ); - - // if there weren't any initial state in url, - // then put _g key into url - if (!initialStateFromUrl) { - urlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, initialState, { - replace: true, - }); - } - - // trigger initial syncing from state container to services if needed - querySyncStateContainer.set(initialState); - - const { start, stop: stopSyncState } = syncState({ - stateStorage: urlStateStorage, - stateContainer: { - ...querySyncStateContainer, - set: state => { - if (state) { - // syncState utils requires to handle incoming "null" value - querySyncStateContainer.set(state); - } - }, - }, - storageKey: GLOBAL_STATE_STORAGE_KEY, - }); - - start(); - return { - stop: () => { - stopSyncState(); - pushQueryStateSubscription.unsubscribe(); - stopPullQueryState(); - }, - hasInheritedQueryFromUrl, - }; -}; - -export const getQueryStateContainer = ( - { timefilter: { timefilter }, filterManager }: QuerySetup, - initialStateOverrides: Partial = {} -) => { - const defaultState: QuerySyncState = { - time: timefilter.getTime(), - refreshInterval: timefilter.getRefreshInterval(), - filters: filterManager.getGlobalFilters(), - }; - - const initialState: QuerySyncState = { - ...defaultState, - ...initialStateOverrides, - }; - - // create state container, which will be used for syncing with syncState() util - const querySyncStateContainer = createStateContainer( - initialState, - { - setTime: (state: QuerySyncState) => (time: TimeRange) => ({ ...state, time }), - setRefreshInterval: (state: QuerySyncState) => (refreshInterval: RefreshInterval) => ({ - ...state, - refreshInterval, - }), - setFilters: (state: QuerySyncState) => (filters: Filter[]) => ({ - ...state, - filters, - }), - }, - { - time: (state: QuerySyncState) => () => state.time, - refreshInterval: (state: QuerySyncState) => () => state.refreshInterval, - filters: (state: QuerySyncState) => () => state.filters, - } - ); - - const subs: Subscription[] = [ - timefilter.getTimeUpdate$().subscribe(() => { - querySyncStateContainer.transitions.setTime(timefilter.getTime()); - }), - timefilter.getRefreshIntervalUpdate$().subscribe(() => { - querySyncStateContainer.transitions.setRefreshInterval(timefilter.getRefreshInterval()); - }), - filterManager - .getUpdates$() - .pipe( - map(() => filterManager.getGlobalFilters()), // we need to track only global filters here - filter(newGlobalFilters => { - // continue only if global filters changed - // and ignore app state filters - const oldGlobalFilters = querySyncStateContainer.get().filters; - return ( - !oldGlobalFilters || - !compareFilters(newGlobalFilters, oldGlobalFilters, COMPARE_ALL_OPTIONS) - ); - }) - ) - .subscribe(newGlobalFilters => { - querySyncStateContainer.transitions.setFilters(newGlobalFilters); - }), - ]; - - return { - querySyncStateContainer, - stop: () => { - subs.forEach(s => s.unsubscribe()); - }, - initialState, - }; -}; diff --git a/src/plugins/data/public/query/state_sync/sync_query.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts similarity index 63% rename from src/plugins/data/public/query/state_sync/sync_query.test.ts rename to src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts index 1e7db2b9fd22f7..50dc35ea955ee3 100644 --- a/src/plugins/data/public/query/state_sync/sync_query.test.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts @@ -31,7 +31,8 @@ import { import { QueryService, QueryStart } from '../query_service'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; import { TimefilterContract } from '../timefilter'; -import { getQueryStateContainer, QuerySyncState, syncQuery } from './sync_query'; +import { syncQueryStateWithUrl } from './sync_state_with_url'; +import { QueryState } from './types'; const setupMock = coreMock.createSetup(); const startMock = coreMock.createStart(); @@ -49,7 +50,7 @@ setupMock.uiSettings.get.mockImplementation((key: string) => { } }); -describe('sync_query', () => { +describe('sync_query_state_with_url', () => { let queryServiceStart: QueryStart; let filterManager: FilterManager; let timefilter: TimefilterContract; @@ -90,7 +91,7 @@ describe('sync_query', () => { }); test('url is actually changed when data in services changes', () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); filterManager.setFilters([gF, aF]); kbnUrlStateStorage.flush(); // sync force location change expect(history.location.hash).toMatchInlineSnapshot( @@ -100,16 +101,16 @@ describe('sync_query', () => { }); test('when filters change, global filters synced to urlStorage', () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); filterManager.setFilters([gF, aF]); - expect(kbnUrlStateStorage.get('_g')?.filters).toHaveLength(1); + expect(kbnUrlStateStorage.get('_g')?.filters).toHaveLength(1); stop(); }); test('when time range changes, time synced to urlStorage', () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); timefilter.setTime({ from: 'now-30m', to: 'now' }); - expect(kbnUrlStateStorage.get('_g')?.time).toEqual({ + expect(kbnUrlStateStorage.get('_g')?.time).toEqual({ from: 'now-30m', to: 'now', }); @@ -117,9 +118,9 @@ describe('sync_query', () => { }); test('when refresh interval changes, refresh interval is synced to urlStorage', () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); timefilter.setRefreshInterval({ pause: true, value: 100 }); - expect(kbnUrlStateStorage.get('_g')?.refreshInterval).toEqual({ + expect(kbnUrlStateStorage.get('_g')?.refreshInterval).toEqual({ pause: true, value: 100, }); @@ -127,7 +128,7 @@ describe('sync_query', () => { }); test('when url is changed, filters synced back to filterManager', () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); kbnUrlStateStorage.cancel(); // stop initial syncing pending update history.push(pathWithFilter); expect(filterManager.getGlobalFilters()).toHaveLength(1); @@ -137,14 +138,17 @@ describe('sync_query', () => { test('initial url should be synced with services', () => { history.push(pathWithFilter); - const { stop, hasInheritedQueryFromUrl } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop, hasInheritedQueryFromUrl } = syncQueryStateWithUrl( + queryServiceStart, + kbnUrlStateStorage + ); expect(hasInheritedQueryFromUrl).toBe(true); expect(filterManager.getGlobalFilters()).toHaveLength(1); stop(); }); test("url changes shouldn't trigger services updates if data didn't change", () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); filterManagerChangeTriggered.mockClear(); history.push(pathWithFilter); @@ -156,76 +160,11 @@ describe('sync_query', () => { }); test("if data didn't change, kbnUrlStateStorage.set shouldn't be called", () => { - const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage); + const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); filterManager.setFilters([gF, aF]); const spy = jest.spyOn(kbnUrlStateStorage, 'set'); filterManager.setFilters([gF]); // global filters didn't change expect(spy).not.toBeCalled(); stop(); }); - - describe('getQueryStateContainer', () => { - test('state is initialized with state from query service', () => { - const { stop, querySyncStateContainer, initialState } = getQueryStateContainer( - queryServiceStart - ); - expect(querySyncStateContainer.getState()).toMatchInlineSnapshot(` - Object { - "filters": Array [], - "refreshInterval": Object { - "pause": true, - "value": 0, - }, - "time": Object { - "from": "now-15m", - "to": "now", - }, - } - `); - expect(initialState).toEqual(querySyncStateContainer.getState()); - stop(); - }); - - test('state takes initial overrides into account', () => { - const { stop, querySyncStateContainer, initialState } = getQueryStateContainer( - queryServiceStart, - { - time: { from: 'now-99d', to: 'now' }, - } - ); - expect(querySyncStateContainer.getState().time).toEqual({ - from: 'now-99d', - to: 'now', - }); - expect(initialState).toEqual(querySyncStateContainer.getState()); - stop(); - }); - - test('when filters change, state container contains updated global filters', () => { - const { stop, querySyncStateContainer } = getQueryStateContainer(queryServiceStart); - filterManager.setFilters([gF, aF]); - expect(querySyncStateContainer.getState().filters).toHaveLength(1); - stop(); - }); - - test('when time range changes, state container contains updated time range', () => { - const { stop, querySyncStateContainer } = getQueryStateContainer(queryServiceStart); - timefilter.setTime({ from: 'now-30m', to: 'now' }); - expect(querySyncStateContainer.getState().time).toEqual({ - from: 'now-30m', - to: 'now', - }); - stop(); - }); - - test('when refresh interval changes, state container contains updated refresh interval', () => { - const { stop, querySyncStateContainer } = getQueryStateContainer(queryServiceStart); - timefilter.setRefreshInterval({ pause: true, value: 100 }); - expect(querySyncStateContainer.getState().refreshInterval).toEqual({ - pause: true, - value: 100, - }); - stop(); - }); - }); }); diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts new file mode 100644 index 00000000000000..cd7058b9f8f1c1 --- /dev/null +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + createStateContainer, + IKbnUrlStateStorage, + syncState, +} from '../../../../kibana_utils/public'; +import { QuerySetup, QueryStart } from '../query_service'; +import { connectToQueryState } from './connect_to_query_state'; +import { QueryState } from './types'; +import { FilterStateStore } from '../../../common/es_query/filters'; + +const GLOBAL_STATE_STORAGE_KEY = '_g'; + +/** + * Helper to setup syncing of global data with the URL + * @param QueryService: either setup or start + * @param kbnUrlStateStorage to use for syncing + */ +export const syncQueryStateWithUrl = ( + query: Pick, + kbnUrlStateStorage: IKbnUrlStateStorage +) => { + const { + timefilter: { timefilter }, + filterManager, + } = query; + const defaultState: QueryState = { + time: timefilter.getTime(), + refreshInterval: timefilter.getRefreshInterval(), + filters: filterManager.getGlobalFilters(), + }; + + // retrieve current state from `_g` url + const initialStateFromUrl = kbnUrlStateStorage.get(GLOBAL_STATE_STORAGE_KEY); + + // remember whether there was info in the URL + const hasInheritedQueryFromUrl = Boolean( + initialStateFromUrl && Object.keys(initialStateFromUrl).length + ); + + // prepare initial state, whatever was in URL takes precedences over current state in services + const initialState: QueryState = { + ...defaultState, + ...initialStateFromUrl, + }; + + const globalQueryStateContainer = createStateContainer(initialState); + const stopSyncingWithStateContainer = connectToQueryState(query, globalQueryStateContainer, { + refreshInterval: true, + time: true, + filters: FilterStateStore.GLOBAL_STATE, + }); + + // if there weren't any initial state in url, + // then put _g key into url + if (!initialStateFromUrl) { + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, initialState, { + replace: true, + }); + } + + // trigger initial syncing from state container to services if needed + globalQueryStateContainer.set(initialState); + + const { start, stop: stopSyncingWithUrl } = syncState({ + stateStorage: kbnUrlStateStorage, + stateContainer: { + ...globalQueryStateContainer, + set: state => { + globalQueryStateContainer.set(state || defaultState); + }, + }, + storageKey: GLOBAL_STATE_STORAGE_KEY, + }); + + start(); + return { + stop: () => { + stopSyncingWithStateContainer(); + stopSyncingWithUrl(); + }, + hasInheritedQueryFromUrl, + }; +}; diff --git a/src/plugins/data/public/query/state_sync/types.ts b/src/plugins/data/public/query/state_sync/types.ts new file mode 100644 index 00000000000000..747d4d45fe29b4 --- /dev/null +++ b/src/plugins/data/public/query/state_sync/types.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Filter, RefreshInterval, TimeRange } from '../../../common'; + +/** + * All query state service state + */ +export interface QueryState { + time?: TimeRange; + refreshInterval?: RefreshInterval; + filters?: Filter[]; +} + +type QueryStateChangePartial = { + [P in keyof QueryState]?: boolean; +}; + +export interface QueryStateChange extends QueryStateChangePartial { + appFilters?: boolean; // specifies if app filters change + globalFilters?: boolean; // specifies if global filters change +} diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 58806a9328b1cd..4fbdac47fb3b02 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -50,8 +50,13 @@ export class Timefilter { private _autoRefreshIntervalId: number = 0; + private readonly timeDefaults: TimeRange; + private readonly refreshIntervalDefaults: RefreshInterval; + constructor(config: TimefilterConfig, timeHistory: TimeHistoryContract) { this._history = timeHistory; + this.timeDefaults = config.timeDefaults; + this.refreshIntervalDefaults = config.refreshIntervalDefaults; this._time = config.timeDefaults; this.setRefreshInterval(config.refreshIntervalDefaults); } @@ -208,6 +213,14 @@ export class Timefilter { this.enabledUpdated$.next(false); }; + public getTimeDefaults(): TimeRange { + return _.cloneDeep(this.timeDefaults); + } + + public getRefreshIntervalDefaults(): RefreshInterval { + return _.cloneDeep(this.refreshIntervalDefaults); + } + private getForceNow = () => { const forceNow = parseQueryString().forceNow as string; if (!forceNow) { diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts index 80c13464ad98a9..7863000b1ace43 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts @@ -43,6 +43,8 @@ const createSetupContractMock = () => { getBounds: jest.fn(), calculateBounds: jest.fn(), createFilter: jest.fn(), + getRefreshIntervalDefaults: jest.fn(), + getTimeDefaults: jest.fn(), }; const historyMock: jest.Mocked = { diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index b411d27a2a965d..58f00ff9ed6578 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -197,6 +197,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], + "state$": Observable { + "_isScalar": false, + }, "timefilter": Object { "history": Object { "add": [MockFunction], @@ -215,8 +218,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "getEnabledUpdated$": [MockFunction], "getFetch$": [MockFunction], "getRefreshInterval": [MockFunction], + "getRefreshIntervalDefaults": [MockFunction], "getRefreshIntervalUpdate$": [MockFunction], "getTime": [MockFunction], + "getTimeDefaults": [MockFunction], "getTimeUpdate$": [MockFunction], "isAutoRefreshSelectorEnabled": [MockFunction], "isTimeRangeSelectorEnabled": [MockFunction], @@ -855,6 +860,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], + "state$": Observable { + "_isScalar": false, + }, "timefilter": Object { "history": Object { "add": [MockFunction], @@ -873,8 +881,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "getEnabledUpdated$": [MockFunction], "getFetch$": [MockFunction], "getRefreshInterval": [MockFunction], + "getRefreshIntervalDefaults": [MockFunction], "getRefreshIntervalUpdate$": [MockFunction], "getTime": [MockFunction], + "getTimeDefaults": [MockFunction], "getTimeUpdate$": [MockFunction], "isAutoRefreshSelectorEnabled": [MockFunction], "isTimeRangeSelectorEnabled": [MockFunction], @@ -1495,6 +1505,9 @@ exports[`QueryStringInput Should pass the query language to the language switche "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], + "state$": Observable { + "_isScalar": false, + }, "timefilter": Object { "history": Object { "add": [MockFunction], @@ -1513,8 +1526,10 @@ exports[`QueryStringInput Should pass the query language to the language switche "getEnabledUpdated$": [MockFunction], "getFetch$": [MockFunction], "getRefreshInterval": [MockFunction], + "getRefreshIntervalDefaults": [MockFunction], "getRefreshIntervalUpdate$": [MockFunction], "getTime": [MockFunction], + "getTimeDefaults": [MockFunction], "getTimeUpdate$": [MockFunction], "isAutoRefreshSelectorEnabled": [MockFunction], "isTimeRangeSelectorEnabled": [MockFunction], @@ -2150,6 +2165,9 @@ exports[`QueryStringInput Should pass the query language to the language switche "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], + "state$": Observable { + "_isScalar": false, + }, "timefilter": Object { "history": Object { "add": [MockFunction], @@ -2168,8 +2186,10 @@ exports[`QueryStringInput Should pass the query language to the language switche "getEnabledUpdated$": [MockFunction], "getFetch$": [MockFunction], "getRefreshInterval": [MockFunction], + "getRefreshIntervalDefaults": [MockFunction], "getRefreshIntervalUpdate$": [MockFunction], "getTime": [MockFunction], + "getTimeDefaults": [MockFunction], "getTimeUpdate$": [MockFunction], "isAutoRefreshSelectorEnabled": [MockFunction], "isTimeRangeSelectorEnabled": [MockFunction], @@ -2790,6 +2810,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], + "state$": Observable { + "_isScalar": false, + }, "timefilter": Object { "history": Object { "add": [MockFunction], @@ -2808,8 +2831,10 @@ exports[`QueryStringInput Should render the given query 1`] = ` "getEnabledUpdated$": [MockFunction], "getFetch$": [MockFunction], "getRefreshInterval": [MockFunction], + "getRefreshIntervalDefaults": [MockFunction], "getRefreshIntervalUpdate$": [MockFunction], "getTime": [MockFunction], + "getTimeDefaults": [MockFunction], "getTimeUpdate$": [MockFunction], "isAutoRefreshSelectorEnabled": [MockFunction], "isTimeRangeSelectorEnabled": [MockFunction], @@ -3445,6 +3470,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], + "state$": Observable { + "_isScalar": false, + }, "timefilter": Object { "history": Object { "add": [MockFunction], @@ -3463,8 +3491,10 @@ exports[`QueryStringInput Should render the given query 1`] = ` "getEnabledUpdated$": [MockFunction], "getFetch$": [MockFunction], "getRefreshInterval": [MockFunction], + "getRefreshIntervalDefaults": [MockFunction], "getRefreshIntervalUpdate$": [MockFunction], "getTime": [MockFunction], + "getTimeDefaults": [MockFunction], "getTimeUpdate$": [MockFunction], "isAutoRefreshSelectorEnabled": [MockFunction], "isTimeRangeSelectorEnabled": [MockFunction], diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 632385e019e4c4..7d65e947c0f042 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../kibana_react/public'; @@ -117,13 +117,28 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) // App name should come from the core application service. // Until it's available, we'll ask the user to provide it for the pre-wired component. return (props: StatefulSearchBarProps) => { + const { useDefaultBehaviors } = props; // Handle queries - const [query, setQuery] = useState( - props.query || { - query: '', - language: core.uiSettings.get('search:queryLanguage'), + const queryRef = useRef(props.query); + const onQuerySubmitRef = useRef(props.onQuerySubmit); + const defaultQuery = { + query: '', + language: core.uiSettings.get('search:queryLanguage'), + }; + const [query, setQuery] = useState(props.query || defaultQuery); + + useEffect(() => { + if (props.query !== queryRef.current) { + queryRef.current = props.query; + setQuery(props.query || defaultQuery); } - ); + }, [defaultQuery, props.query]); + + useEffect(() => { + if (props.onQuerySubmit !== onQuerySubmitRef.current) { + onQuerySubmitRef.current = props.onQuerySubmit; + } + }, [props.onQuerySubmit]); // handle service state updates. // i.e. filters being added from a visualization directly to filterManager. @@ -150,16 +165,15 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) // Fire onQuerySubmit on query or timerange change useEffect(() => { - if (!props.useDefaultBehaviors) return; - if (props.onQuerySubmit) - props.onQuerySubmit( - { - dateRange: timeRange, - query, - }, - true - ); - }, [props, props.onQuerySubmit, props.useDefaultBehaviors, query, timeRange]); + if (!useDefaultBehaviors || !onQuerySubmitRef.current) return; + onQuerySubmitRef.current( + { + dateRange: timeRange, + query, + }, + true + ); + }, [query, timeRange, useDefaultBehaviors]); return ( Date: Thu, 27 Feb 2020 16:08:37 +0100 Subject: [PATCH 02/28] [Discover] Fix incorrect filter generated by "Filter for field present" (#58586) * Fix _exists_ filter handling * Add functional test --- .../filter_manager/lib/generate_filters.ts | 5 +++-- test/functional/apps/context/_filters.js | 16 +++++++++++++--- test/functional/services/doc_table.ts | 19 +++++++++++++++++-- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index 105e932f696f07..4220df7b1a49b7 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -103,11 +103,12 @@ export function generateFilters( filter = existing; } else { const tmpIndexPattern = { id: index } as IIndexPattern; - + // exists filter special case: fieldname = '_exists' and value = fieldname const filterType = fieldName === '_exists_' ? FILTERS.EXISTS : FILTERS.PHRASE; + const actualFieldObj = fieldName === '_exists_' ? ({ name: value } as IFieldType) : fieldObj; filter = buildFilter( tmpIndexPattern, - fieldObj, + actualFieldObj, filterType, negate, false, diff --git a/test/functional/apps/context/_filters.js b/test/functional/apps/context/_filters.js index 4cd3f1a54b7717..c9499f5a805ab3 100644 --- a/test/functional/apps/context/_filters.js +++ b/test/functional/apps/context/_filters.js @@ -39,14 +39,13 @@ export default function({ getService, getPageObjects }) { }); }); - it('should be addable via expanded doc table rows', async function() { + it('inclusive filter should be addable via expanded doc table rows', async function() { await docTable.toggleRowExpanded({ isAnchorRow: true }); await retry.try(async () => { const anchorDetailsRow = await docTable.getAnchorDetailsRow(); await docTable.addInclusiveFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - // await docTable.toggleRowExpanded({ isAnchorRow: true }); expect( await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true) ).to.be(true); @@ -58,7 +57,7 @@ export default function({ getService, getPageObjects }) { }); }); - it('should be toggleable via the filter bar', async function() { + it('inclusive filter should be toggleable via the filter bar', async function() { await filterBar.addFilter(TEST_ANCHOR_FILTER_FIELD, 'IS', TEST_ANCHOR_FILTER_VALUE); await PageObjects.context.waitUntilContextLoadingHasFinished(); // disable filter @@ -76,5 +75,16 @@ export default function({ getService, getPageObjects }) { expect(hasOnlyFilteredRows).to.be(false); }); }); + + it('filter for presence should be addable via expanded doc table rows', async function() { + await docTable.toggleRowExpanded({ isAnchorRow: true }); + + await retry.try(async () => { + const anchorDetailsRow = await docTable.getAnchorDetailsRow(); + await docTable.addExistsFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + expect(await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, 'exists', true)).to.be(true); + }); + }); }); } diff --git a/test/functional/services/doc_table.ts b/test/functional/services/doc_table.ts index 6957b0fa999294..2530831e0f6f9b 100644 --- a/test/functional/services/doc_table.ts +++ b/test/functional/services/doc_table.ts @@ -98,13 +98,12 @@ export function DocTableProvider({ getService, getPageObjects }: FtrProviderCont const $ = await table.parseDomContent(); const rowLocator = options.isAnchorRow ? '~docTableAnchorRow' : '~docTableRow'; const rows = $.findTestSubjects(rowLocator).toArray(); - const fields = rows.map((row: any) => + return rows.map((row: any) => $(row) .find('[data-test-subj~="docTableField"]') .toArray() .map((field: any) => $(field).text()) ); - return fields; } public async getHeaderFields(): Promise { @@ -144,6 +143,22 @@ export function DocTableProvider({ getService, getPageObjects }: FtrProviderCont await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); } + public async getAddExistsFilterButton( + tableDocViewRow: WebElementWrapper + ): Promise { + return await tableDocViewRow.findByCssSelector(`[data-test-subj~="addExistsFilterButton"]`); + } + + public async addExistsFilter( + detailsRow: WebElementWrapper, + fieldName: WebElementWrapper + ): Promise { + const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); + const addInclusiveFilterButton = await this.getAddExistsFilterButton(tableDocViewRow); + await addInclusiveFilterButton.click(); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + } + public async toggleRowExpanded( options: SelectOptions = { isAnchorRow: false, rowIndex: 0 } ): Promise { From a06cc315832b178ab7b3f46610d92e4b5a491837 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Thu, 27 Feb 2020 11:09:43 -0500 Subject: [PATCH 03/28] [SIEM] [Detection Engine] Remove unnecessary ts-ignores (#58689) * fixes from comments on previous PR * replaces any with unknown, adds test to show how optional chaining of array items can result in undefined even though typescript says its not --- .../routes/rules/find_rules_status_route.ts | 4 ++++ .../lib/detection_engine/routes/utils.test.ts | 16 ++++++++++++++++ .../server/lib/detection_engine/routes/utils.ts | 5 +++-- .../detection_engine/rules/read_rules.test.ts | 9 ++++----- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index c496c7b7ce59cd..5687c5d4095db1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -59,6 +59,10 @@ export const createFindRulesStatusRoute = (getClients: GetScopedClients): Hapi.S searchFields: ['alertId'], }); const accumulated = await acc; + + // Array accessors can result in undefined but + // this is not represented in typescript for some reason, + // https://github.com/Microsoft/TypeScript/issues/11122 const currentStatus = convertToSnakeCase( lastFiveErrorsForId.saved_objects[0]?.attributes ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index 3148083b4db267..a382c4a3236719 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -6,6 +6,8 @@ import Boom from 'boom'; +import { SavedObjectsFindResponse } from 'kibana/server'; +import { IRuleSavedAttributesSavedObjectAttributes, IRuleStatusAttributes } from '../rules/types'; import { transformError, transformBulkError, @@ -323,5 +325,19 @@ describe('utils', () => { const values = {}; expect(convertToSnakeCase(values)).toEqual({}); }); + it('returns null when passed in undefined', () => { + // Array accessors can result in undefined but + // this is not represented in typescript for some reason, + // https://github.com/Microsoft/TypeScript/issues/11122 + const values: SavedObjectsFindResponse = { + page: 0, + per_page: 5, + total: 0, + saved_objects: [], + }; + expect( + convertToSnakeCase(values.saved_objects[0]?.attributes) // this is undefined, but it says it's not + ).toEqual(null); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index aaa5db7966b2b3..65c9141619cb9e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -222,8 +222,9 @@ export const getIndex = (getSpaceId: () => string, config: LegacyServices['confi return `${signalsIndex}-${spaceId}`; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const convertToSnakeCase = >(obj: T): Partial | null => { +export const convertToSnakeCase = >( + obj: T +): Partial | null => { if (!obj) { return null; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts index aa1cce6f152382..862ea9d2dcbe5d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts @@ -39,9 +39,9 @@ describe('read_rules', () => { }); test('should return null if saved object found by alerts client given id is not alert type', async () => { const alertsClient = alertsClientMock.create(); - const { alertTypeId, ...rest } = getResult(); - // @ts-ignore - alertsClient.get.mockImplementation(() => rest); + const result = getResult(); + delete result.alertTypeId; + alertsClient.get.mockResolvedValue(result); const rule = await readRules({ alertsClient, @@ -109,8 +109,7 @@ describe('read_rules', () => { test('should return null if the output from alertsClient with ruleId set is empty', async () => { const alertsClient = alertsClientMock.create(); alertsClient.get.mockResolvedValue(getResult()); - // @ts-ignore - alertsClient.find.mockResolvedValue({ data: [] }); + alertsClient.find.mockResolvedValue({ data: [], page: 0, perPage: 1, total: 0 }); const rule = await readRules({ alertsClient, From 515348438bce26b3c09989ba85b58dd79f6de0da Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Thu, 27 Feb 2020 08:14:45 -0800 Subject: [PATCH 04/28] Fixed connector and alerts view flashing empty state before loading list (#58693) --- .../components/actions_connectors_list.tsx | 4 +++- .../sections/alerts_list/components/alerts_list.tsx | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index bed285f668e01f..f48e27791419dc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -14,6 +14,7 @@ import { EuiEmptyPrompt, EuiTitle, EuiLink, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -370,8 +371,9 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> {/* Render the view based on if there's data or if they can save */} + {(isLoadingActions || isLoadingActionTypes) && } {data.length !== 0 && table} - {data.length === 0 && canSave && emptyPrompt} + {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && emptyPrompt} {data.length === 0 && !canSave && noPermissionPrompt} { {convertAlertsToTableItems(alertsState.data, alertTypesState.data).length !== 0 && table} {convertAlertsToTableItems(alertsState.data, alertTypesState.data).length === 0 && + !alertTypesState.isLoading && + !alertsState.isLoading && emptyPrompt} + {(alertTypesState.isLoading || alertsState.isLoading) && } Date: Thu, 27 Feb 2020 08:28:08 -0800 Subject: [PATCH 05/28] [DOCS] Fixes outdated monitoring links (#58556) --- docs/user/monitoring/elasticsearch-details.asciidoc | 2 +- .../server/spec/generated/monitoring.bulk.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/monitoring/elasticsearch-details.asciidoc b/docs/user/monitoring/elasticsearch-details.asciidoc index 2990e965be03c4..c0e804672d2986 100644 --- a/docs/user/monitoring/elasticsearch-details.asciidoc +++ b/docs/user/monitoring/elasticsearch-details.asciidoc @@ -14,7 +14,7 @@ the <>, <>, [role="screenshot"] image::user/monitoring/images/monitoring-elasticsearch.jpg["Monitoring clusters"] -See also {ref}/es-monitoring.html[Monitoring {es}]. +See also {ref}/monitor-elasticsearch-cluster.html[Monitor a cluster]. [float] [[cluster-overview-page]] diff --git a/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json b/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json index 2b27950e7b0974..26a9078f73ce82 100644 --- a/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json +++ b/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json @@ -12,6 +12,6 @@ "patterns": [ "_monitoring/bulk" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/es-monitoring.html" + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/monitor-elasticsearch-cluster.html" } } From 43be649f5006219421edaf151115d8240b0d7d57 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Thu, 27 Feb 2020 09:32:44 -0700 Subject: [PATCH 06/28] [Metrics UI] Ensure inventory view buckets never drop below 60 seconds (#58503) * [Metrics UI] Ensure inventory view buckets never drop below 60 seconds * Fixing tests * Fixing tests... again * Fixing tests... rounding issue? * Trying to fix the tests... again * updating test for custom metric Co-authored-by: Elastic Machine --- .../create_timerange_with_interval.ts | 6 +++-- .../test/api_integration/apis/infra/waffle.ts | 24 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts index 6f036475a1e1cf..cf2b1e59b2a225 100644 --- a/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts +++ b/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts @@ -21,7 +21,7 @@ export const createTimeRangeWithInterval = async ( ): Promise => { const aggregations = getMetricsAggregations(options); const modules = await aggregationsToModules(framework, requestContext, aggregations, options); - const interval = + const interval = Math.max( (await calculateMetricInterval( framework, requestContext, @@ -32,7 +32,9 @@ export const createTimeRangeWithInterval = async ( }, modules, options.nodeType - )) || 60000; + )) || 60, + 60 + ); return { interval: `${interval}s`, from: options.timerange.to - interval * 5000, // We need at least 5 buckets worth of data diff --git a/x-pack/test/api_integration/apis/infra/waffle.ts b/x-pack/test/api_integration/apis/infra/waffle.ts index 3413fc283556c7..26d8c9d265a6a6 100644 --- a/x-pack/test/api_integration/apis/infra/waffle.ts +++ b/x-pack/test/api_integration/apis/infra/waffle.ts @@ -192,9 +192,9 @@ export default function({ getService }: FtrProviderContext) { expect(firstNode).to.have.property('metric'); expect(firstNode.metric).to.eql({ name: 'cpu', - value: 0.009285714285714286, - max: 0.009285714285714286, - avg: 0.0015476190476190477, + value: 0.0032, + max: 0.0038333333333333336, + avg: 0.0027944444444444444, }); } }); @@ -231,9 +231,9 @@ export default function({ getService }: FtrProviderContext) { expect(firstNode).to.have.property('metric'); expect(firstNode.metric).to.eql({ name: 'custom', - value: 0.0041964285714285714, - max: 0.0041964285714285714, - avg: 0.0006994047619047619, + value: 0.0016, + max: 0.0018333333333333333, + avg: 0.0013666666666666669, }); } }); @@ -320,9 +320,9 @@ export default function({ getService }: FtrProviderContext) { expect(firstNode).to.have.property('metric'); expect(firstNode.metric).to.eql({ name: 'cpu', - value: 0.009285714285714286, - max: 0.009285714285714286, - avg: 0.0015476190476190477, + value: 0.0032, + max: 0.0038333333333333336, + avg: 0.0027944444444444444, }); const secondNode = nodes[1]; expect(secondNode).to.have.property('path'); @@ -332,9 +332,9 @@ export default function({ getService }: FtrProviderContext) { expect(secondNode).to.have.property('metric'); expect(secondNode.metric).to.eql({ name: 'cpu', - value: 0.009285714285714286, - max: 0.009285714285714286, - avg: 0.0015476190476190477, + value: 0.0032, + max: 0.0038333333333333336, + avg: 0.0027944444444444444, }); } }); From 235b3535e2fb59894a4624f4d56183502adbee47 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Thu, 27 Feb 2020 10:40:07 -0600 Subject: [PATCH 07/28] [DOCS] Consolidates map content (#57736) * [DOCS] Consolidates map content * Section reorganization * Navigation options * Added images and cleaned up text * Added tilemap.html to redirects * Review comments --- .../visualize_coordinate_map_example.png | Bin 0 -> 397956 bytes docs/images/visualize_heat_map_example.png | Bin 0 -> 51515 bytes docs/images/visualize_region_map_example.png | Bin 0 -> 437327 bytes docs/redirects.asciidoc | 5 +- docs/setup/settings.asciidoc | 8 +- docs/user/visualize.asciidoc | 8 +- docs/visualize/heatmap.asciidoc | 40 ----- docs/visualize/metric.asciidoc | 16 -- docs/visualize/regionmap.asciidoc | 53 ------- docs/visualize/tilemap.asciidoc | 139 +++++++++++++++--- 10 files changed, 128 insertions(+), 141 deletions(-) create mode 100644 docs/images/visualize_coordinate_map_example.png create mode 100644 docs/images/visualize_heat_map_example.png create mode 100644 docs/images/visualize_region_map_example.png delete mode 100644 docs/visualize/heatmap.asciidoc delete mode 100644 docs/visualize/metric.asciidoc delete mode 100644 docs/visualize/regionmap.asciidoc diff --git a/docs/images/visualize_coordinate_map_example.png b/docs/images/visualize_coordinate_map_example.png new file mode 100644 index 0000000000000000000000000000000000000000..24f03376adadeb3bdc32ca480a8054977ac41242 GIT binary patch literal 397956 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 z z!*kC)XU?NP`?Ei@*-^aZ->zkKU;gr!tux51ADm}5CT5>=ardk|KAZ6}YmwPY%+kU+rVUag*Cq|l zlchP}gL4b4aH4r|9rNE@U6=e?_3Sg&Zh;|=)!TB0ff03kd2uG}-2YVA|M271W}LgM z8K2BR_Onw`9VRa6^P#A$JWjo5owh5?6xy-1M|z=UT3WWu(go}W`7A8m4p(l@Ygy?* z)e-F}wPVCsbPV7V*<8nP`1mt?GXw1Dmj>b%VbONKEpoVc9$d{iF5V|+mcp?1Z(<|w zfxf$8aOaT9Y(7lPENb6BEep%_!8?sB@)m{lEZcT?h@ELM|MmE;_pFO+f?^p)W%o%{ z@7YGcwT!>A`}JgM+YYFnkzYQv!vi!rg)0uNh7O>jhQS5FKB>*gyxcS)Old^0-8+Wl z*~aklT#Dj}y4NlOT?D!abP;G90qiTY6_T*Y27>+SK$@NH8_*zkIP5t%rlq;Jh{H?ZxgJoejV_3d{1IPn2WuR5>G_F|3aoFH&ffnfqMI9*akq##d&KDdy zIBwV{h_uwX4?p}+CoSpJJn1s^&(m;auO0RsVSg>0HFH}2eo3?O&dCAA9z4+c<3Ijm z`we|&B0=}+)vM-k`kTM`8$14;eOaD+{<-i|KlM}Lhd=yb_>JHA4cn B|z(z{W5>qATW`M={`G(Mud^8u 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

>m+|Fa4CI5 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

*r=!lU#7N=j{}^-m7=TjYxc3t z`77HvfPe$-M};koMegJ{)7Yj4NsX`RbdB?C7+*`;7Jh|x;1|YMXwE26Ca(k>;$u9# zLfh)Q^w<`4!mNETHoiiK_&AlSQ+dV#mCK*kNsYs)+oENwtwnxgtD^qmeb*a}`XwQu zz>kjttP|?iu4~0ri{0TVZ{yy*Av+d?v z?zG?E{C}=({H{!cuDaq934j{80Oz(l{$w56x3za=E`nwZw=xZW>1AnVv~Yn+&mPD5 zCN&&Jv577rL@Xvp7gM8JrkeS(Hzf}v(g~=1%b`zyf3k&@!bS|P{ zo$%&c!)3ZQMnVh(L$$TRU2b=1GtG@qtPe1?BCt_D^^~46p?S!r$)xG6x5wB$f4;*` zQ`}!~q!2;Mi|*-w6z4t2rK?RVZS=e|uO zXqX{CK6lw)|JKhZnGj~tXt2B`lR0pyS0_iNXmjlVwN1BUkC7?bJR3AcNbmM*ELuM4;t*6Y5DniYTMN^ zQMy&8YF)MNsHn2{leW<$3E7rgkK?+!@XHifQ`~&(?bcnq##*_l99OTDHWB`Ys_P&X z!N)7FzUCTa2r*ex<1?pgmm}rBCtsJ6)KS|lv!~_1Y4_cu_4vuXPP9Azc#nOyaG^}8 zM%m53zn-EzfHOCj?XgD{nQFDR;lp0C#~*!AS~SbNPL-wcg>Zh@n{TNi<)_y5 z=*}+u8znP5U=1xXp^@Y4r1!C zx!YD8z-?>l1g`Ns@j7h@hp^d?=b?^qc%|r&(AD_@;R)Ag2Yv|Gzz6


hGzb|Amf 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 diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 0749ae1d30e9e2..1eabf2441da4f2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -17,6 +17,7 @@ import { EuiButtonEmpty, EuiButton, EuiFlyoutBody, + EuiBetaBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; @@ -146,6 +147,17 @@ export const ConnectorAddFlyout = () => { actionTypeName: actionType.name, }} /> +   + @@ -159,6 +171,17 @@ export const ConnectorAddFlyout = () => { defaultMessage="Select a connector" id="xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle" /> +   + )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index 55386ec6d61f9e..64862927256603 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -5,7 +5,7 @@ */ import React, { useCallback, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup } from '@elastic/eui'; +import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup, EuiBetaBadge } from '@elastic/eui'; import { EuiModal, EuiButton, @@ -129,6 +129,17 @@ export const ConnectorAddModal = ({ actionTypeName: actionType.name, }} /> +   + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index f7ad6f95d048f8..6fe555fd74b398 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -16,6 +16,7 @@ import { EuiFlyoutFooter, EuiButtonEmpty, EuiButton, + EuiBetaBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; @@ -96,6 +97,17 @@ export const ConnectorEditFlyout = ({ initialConnector }: ConnectorEditProps) => defaultMessage="Edit connector" id="xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle" /> +   + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx index a88f916346985a..20ba9f5a497153 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_add.tsx @@ -16,6 +16,7 @@ import { EuiButton, EuiFlyoutBody, EuiPortal, + EuiBetaBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useAlertsContext } from '../../context/alerts_context'; @@ -136,6 +137,16 @@ export const AlertAdd = ({ consumer, canChangeTrigger, alertTypeId }: AlertAddPr defaultMessage="Create Alert" id="xpack.triggersActionsUI.sections.alertAdd.flyoutTitle" /> +   + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index d2cf2decc4a160..2625768dc72421 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -8,9 +8,17 @@ import uuid from 'uuid'; import { shallow } from 'enzyme'; import { AlertDetails } from './alert_details'; import { Alert, ActionType } from '../../../../types'; -import { EuiTitle, EuiBadge, EuiFlexItem, EuiButtonEmpty, EuiSwitch } from '@elastic/eui'; +import { + EuiTitle, + EuiBadge, + EuiFlexItem, + EuiButtonEmpty, + EuiSwitch, + EuiBetaBadge, +} from '@elastic/eui'; import { times, random } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; jest.mock('../../../app_context', () => ({ useAppDependencies: jest.fn(() => ({ @@ -54,7 +62,19 @@ describe('alert_details', () => { ).containsMatchingElement( -

{alert.name}

+

+ {alert.name} +   + +

) ).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 9c3b69962879f3..1952e35c229242 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -21,8 +21,10 @@ import { EuiSwitch, EuiCallOut, EuiSpacer, + EuiBetaBadge, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { useAppDependencies } from '../../../app_context'; import { hasSaveAlertsCapability } from '../../../lib/capabilities'; import { Alert, AlertType, ActionType } from '../../../../types'; @@ -66,7 +68,20 @@ export const AlertDetails: React.FunctionComponent = ({ -

{alert.name}

+

+ {alert.name} +   + +

diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts index 307f39382a2363..f049406b639c79 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts @@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await log.debug('Checking for section heading to say Triggers and Actions.'); const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText(); - expect(headingText).to.be('Alerts and Actions'); + expect(headingText).to.be('Alerts and Actions BETA'); }); describe('Connectors tab', () => { From 841e64e0c1a56e00b50394ff85567f2f6a20faab Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 27 Feb 2020 14:19:24 -0700 Subject: [PATCH 14/28] =?UTF-8?q?run=20jest=20with=20`--detectOpenHandles`?= =?UTF-8?q?=20on=20CI=20to=20figure=20out=20what=20i=E2=80=A6=20(#58543)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * run jest with `--detectOpenHandles` on CI to figure out what is happening with pauses * focus tests on jest integration * force kill child processes in config reload test * skip flaky suite * increase timeout for looking for installed packages * run all tests again --- .../reload_logging_config.test.ts | 318 +++++++++--------- .../installed_packages.test.ts | 2 +- tasks/test_jest.js | 2 +- test/scripts/jenkins_xpack.sh | 2 +- 4 files changed, 159 insertions(+), 165 deletions(-) diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.ts b/src/cli/serve/integration_tests/reload_logging_config.test.ts index 2def3569828d34..9ad8438c312a1e 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.ts +++ b/src/cli/serve/integration_tests/reload_logging_config.test.ts @@ -84,180 +84,174 @@ function createConfigManager(configPath: string) { } describe('Server logging configuration', function() { - let child: Child.ChildProcess; + let child: undefined | Child.ChildProcess; + beforeEach(() => { Fs.mkdirSync(tempDir, { recursive: true }); }); afterEach(async () => { if (child !== undefined) { - child.kill(); - // wait for child to be killed otherwise jest complains that process not finished - await new Promise(res => setTimeout(res, 1000)); + const exitPromise = new Promise(resolve => child?.once('exit', resolve)); + child.kill('SIGKILL'); + await exitPromise; } + Del.sync(tempDir, { force: true }); }); - const isWindows = /^win/.test(process.platform); - if (isWindows) { + if (process.platform.startsWith('win')) { it('SIGHUP is not a feature of Windows.', () => { // nothing to do for Windows }); - } else { - describe('legacy logging', () => { - it( - 'should be reloadable via SIGHUP process signaling', - async function() { - const configFilePath = Path.resolve(tempDir, 'kibana.yml'); - Fs.copyFileSync(legacyConfig, configFilePath); - - child = Child.spawn(process.execPath, [ - kibanaPath, - '--oss', - '--config', - configFilePath, - '--verbose', - ]); - - const message$ = Rx.fromEvent(child.stdout, 'data').pipe( - map(messages => - String(messages) - .split('\n') - .filter(Boolean) - ) - ); - - await message$ - .pipe( - // We know the sighup handler will be registered before this message logged - filter(messages => messages.some(m => m.includes('setting up root'))), - take(1) - ) - .toPromise(); - - const lastMessage = await message$.pipe(take(1)).toPromise(); - expect(containsJsonOnly(lastMessage)).toBe(true); - - createConfigManager(configFilePath).modify(oldConfig => { - oldConfig.logging.json = false; - return oldConfig; - }); - - child.kill('SIGHUP'); - - await message$ - .pipe( - filter(messages => !containsJsonOnly(messages)), - take(1) - ) - .toPromise(); - }, - minute - ); - - it( - 'should recreate file handle on SIGHUP', - async function() { - const logPath = Path.resolve(tempDir, 'kibana.log'); - const logPathArchived = Path.resolve(tempDir, 'kibana_archive.log'); - - child = Child.spawn(process.execPath, [ - kibanaPath, - '--oss', - '--config', - legacyConfig, - '--logging.dest', - logPath, - '--verbose', - ]); - - await watchFileUntil(logPath, /setting up root/, 30 * second); - // once the server is running, archive the log file and issue SIGHUP - Fs.renameSync(logPath, logPathArchived); - child.kill('SIGHUP'); - - await watchFileUntil( - logPath, - /Reloaded logging configuration due to SIGHUP/, - 30 * second - ); - }, - minute - ); - }); - - describe('platform logging', () => { - it( - 'should be reloadable via SIGHUP process signaling', - async function() { - const configFilePath = Path.resolve(tempDir, 'kibana.yml'); - Fs.copyFileSync(configFileLogConsole, configFilePath); - - child = Child.spawn(process.execPath, [kibanaPath, '--oss', '--config', configFilePath]); - - const message$ = Rx.fromEvent(child.stdout, 'data').pipe( - map(messages => - String(messages) - .split('\n') - .filter(Boolean) - ) - ); - - await message$ - .pipe( - // We know the sighup handler will be registered before this message logged - filter(messages => messages.some(m => m.includes('setting up root'))), - take(1) - ) - .toPromise(); - - const lastMessage = await message$.pipe(take(1)).toPromise(); - expect(containsJsonOnly(lastMessage)).toBe(true); - - createConfigManager(configFilePath).modify(oldConfig => { - oldConfig.logging.appenders.console.layout.kind = 'pattern'; - return oldConfig; - }); - child.kill('SIGHUP'); - - await message$ - .pipe( - filter(messages => !containsJsonOnly(messages)), - take(1) - ) - .toPromise(); - }, - 30 * second - ); - it( - 'should recreate file handle on SIGHUP', - async function() { - const configFilePath = Path.resolve(tempDir, 'kibana.yml'); - Fs.copyFileSync(configFileLogFile, configFilePath); - - const logPath = Path.resolve(tempDir, 'kibana.log'); - const logPathArchived = Path.resolve(tempDir, 'kibana_archive.log'); - - createConfigManager(configFilePath).modify(oldConfig => { - oldConfig.logging.appenders.file.path = logPath; - return oldConfig; - }); - - child = Child.spawn(process.execPath, [kibanaPath, '--oss', '--config', configFilePath]); - - await watchFileUntil(logPath, /setting up root/, 30 * second); - // once the server is running, archive the log file and issue SIGHUP - Fs.renameSync(logPath, logPathArchived); - child.kill('SIGHUP'); - - await watchFileUntil( - logPath, - /Reloaded logging configuration due to SIGHUP/, - 30 * second - ); - }, - minute - ); - }); + return; } + + describe('legacy logging', () => { + it( + 'should be reloadable via SIGHUP process signaling', + async function() { + const configFilePath = Path.resolve(tempDir, 'kibana.yml'); + Fs.copyFileSync(legacyConfig, configFilePath); + + child = Child.spawn(process.execPath, [ + kibanaPath, + '--oss', + '--config', + configFilePath, + '--verbose', + ]); + + const message$ = Rx.fromEvent(child.stdout, 'data').pipe( + map(messages => + String(messages) + .split('\n') + .filter(Boolean) + ) + ); + + await message$ + .pipe( + // We know the sighup handler will be registered before this message logged + filter(messages => messages.some(m => m.includes('setting up root'))), + take(1) + ) + .toPromise(); + + const lastMessage = await message$.pipe(take(1)).toPromise(); + expect(containsJsonOnly(lastMessage)).toBe(true); + + createConfigManager(configFilePath).modify(oldConfig => { + oldConfig.logging.json = false; + return oldConfig; + }); + + child.kill('SIGHUP'); + + await message$ + .pipe( + filter(messages => !containsJsonOnly(messages)), + take(1) + ) + .toPromise(); + }, + minute + ); + + it( + 'should recreate file handle on SIGHUP', + async function() { + const logPath = Path.resolve(tempDir, 'kibana.log'); + const logPathArchived = Path.resolve(tempDir, 'kibana_archive.log'); + + child = Child.spawn(process.execPath, [ + kibanaPath, + '--oss', + '--config', + legacyConfig, + '--logging.dest', + logPath, + '--verbose', + ]); + + await watchFileUntil(logPath, /setting up root/, 30 * second); + // once the server is running, archive the log file and issue SIGHUP + Fs.renameSync(logPath, logPathArchived); + child.kill('SIGHUP'); + + await watchFileUntil(logPath, /Reloaded logging configuration due to SIGHUP/, 30 * second); + }, + minute + ); + }); + + describe('platform logging', () => { + it( + 'should be reloadable via SIGHUP process signaling', + async function() { + const configFilePath = Path.resolve(tempDir, 'kibana.yml'); + Fs.copyFileSync(configFileLogConsole, configFilePath); + + child = Child.spawn(process.execPath, [kibanaPath, '--oss', '--config', configFilePath]); + + const message$ = Rx.fromEvent(child.stdout, 'data').pipe( + map(messages => + String(messages) + .split('\n') + .filter(Boolean) + ) + ); + + await message$ + .pipe( + // We know the sighup handler will be registered before this message logged + filter(messages => messages.some(m => m.includes('setting up root'))), + take(1) + ) + .toPromise(); + + const lastMessage = await message$.pipe(take(1)).toPromise(); + expect(containsJsonOnly(lastMessage)).toBe(true); + + createConfigManager(configFilePath).modify(oldConfig => { + oldConfig.logging.appenders.console.layout.kind = 'pattern'; + return oldConfig; + }); + child.kill('SIGHUP'); + + await message$ + .pipe( + filter(messages => !containsJsonOnly(messages)), + take(1) + ) + .toPromise(); + }, + 30 * second + ); + it( + 'should recreate file handle on SIGHUP', + async function() { + const configFilePath = Path.resolve(tempDir, 'kibana.yml'); + Fs.copyFileSync(configFileLogFile, configFilePath); + + const logPath = Path.resolve(tempDir, 'kibana.log'); + const logPathArchived = Path.resolve(tempDir, 'kibana_archive.log'); + + createConfigManager(configFilePath).modify(oldConfig => { + oldConfig.logging.appenders.file.path = logPath; + return oldConfig; + }); + + child = Child.spawn(process.execPath, [kibanaPath, '--oss', '--config', configFilePath]); + + await watchFileUntil(logPath, /setting up root/, 30 * second); + // once the server is running, archive the log file and issue SIGHUP + Fs.renameSync(logPath, logPathArchived); + child.kill('SIGHUP'); + + await watchFileUntil(logPath, /Reloaded logging configuration due to SIGHUP/, 30 * second); + }, + minute + ); + }); }); diff --git a/src/dev/npm/integration_tests/installed_packages.test.ts b/src/dev/npm/integration_tests/installed_packages.test.ts index 5c942005d2eee3..75cd0e56076982 100644 --- a/src/dev/npm/integration_tests/installed_packages.test.ts +++ b/src/dev/npm/integration_tests/installed_packages.test.ts @@ -41,7 +41,7 @@ describe('src/dev/npm/installed_packages', () => { includeDev: true, }), ]); - }, 60 * 1000); + }, 360 * 1000); it('reads all installed packages of a module', () => { expect(fixture1Packages).toEqual([ diff --git a/tasks/test_jest.js b/tasks/test_jest.js index ff1a941610ad9d..bcb05a83675e5e 100644 --- a/tasks/test_jest.js +++ b/tasks/test_jest.js @@ -33,7 +33,7 @@ module.exports = function(grunt) { function runJest(jestScript) { const serverCmd = { cmd: 'node', - args: [jestScript, '--ci'], + args: [jestScript, '--ci', '--detectOpenHandles'], opts: { stdio: 'inherit' }, }; diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 3d30496ecb5822..b629e064b39b5e 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -11,7 +11,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> Running jest tests" cd "$XPACK_DIR" - checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose + checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose --detectOpenHandles echo "" echo "" From 2a03dffdad6c1dbeaf9a541c4ea0fb84183a8ca8 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Thu, 27 Feb 2020 22:45:53 +0100 Subject: [PATCH 15/28] [SIEM] Fix Timeline registerProvider to be called only when it's needed (#58051) --- .../drag_and_drop/draggable_wrapper.test.tsx | 31 +++++- .../drag_and_drop/draggable_wrapper.tsx | 99 +++++++++++-------- 2 files changed, 90 insertions(+), 40 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx index 92adc1a9adb7a2..d34f4cce9fea41 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx @@ -12,7 +12,7 @@ import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; import { mockDataProviders } from '../timeline/data_providers/mock/mock_data_providers'; import { DragDropContextWrapper } from './drag_drop_context_wrapper'; -import { DraggableWrapper } from './draggable_wrapper'; +import { DraggableWrapper, ConditionalPortal } from './draggable_wrapper'; import { useMountAppended } from '../../utils/use_mount_appended'; describe('DraggableWrapper', () => { @@ -84,3 +84,32 @@ describe('DraggableWrapper', () => { }); }); }); + +describe('ConditionalPortal', () => { + const mount = useMountAppended(); + const props = { + usePortal: false, + registerProvider: jest.fn(), + isDragging: true, + }; + + it(`doesn't call registerProvider is NOT isDragging`, () => { + mount( + +
+ + + + + @@ -3407,7 +3371,7 @@ tr:hover .c3:focus::before {
- - - -
- - -
- - - - + - - -
- - - - - - + - + title="sha1: fa5195a..." + > - - sha1: fa5195a... - - + + - - - - - + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + + - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
+ + + + + + + + +
+ + + + + + + + + + + + + @@ -3709,7 +3655,7 @@ tr:hover .c3:focus::before {
- - - -
- - -
- - - - + - - -
- - - - - - + - + title="md5: f7653f1..." + > - - md5: f7653f1... - - + + - - - - - + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + + - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
+ + + + + + + + +
+ + + + + + + + + + + + + From b6dfcc7ad790cd85dd0071124dc23203281f3f87 Mon Sep 17 00:00:00 2001 From: igoristic Date: Thu, 27 Feb 2020 21:46:45 -0500 Subject: [PATCH 22/28] Removed unused indices (#57903) Co-authored-by: Elastic Machine --- .../plugins/monitoring/common/constants.ts | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts index 1fb6acdb915b88..9a4030f3eb2147 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.ts +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -141,23 +141,12 @@ export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notificat export const STANDALONE_CLUSTER_CLUSTER_UUID = '__standalone_cluster__'; -const INDEX_PATTERN_NEW = ',monitoring-*-7-*,monitoring-*-8-*'; -const INDEX_PATTERN_KIBANA_NEW = ',monitoring-kibana-7-*,monitoring-kibana-8-*'; -const INDEX_PATTERN_LOGSTASH_NEW = ',monitoring-logstash-7-*,monitoring-logstash-8-*'; -const INDEX_PATTERN_BEATS_NEW = ',monitoring-beats-7-*,monitoring-beats-8-*'; -const INDEX_ALERTS_NEW = ',monitoring-alerts-7,monitoring-alerts-8'; -const INDEX_PATTERN_ELASTICSEARCH_NEW = ',monitoring-es-7-*,monitoring-es-8-*'; - -export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*' + INDEX_PATTERN_NEW; -export const INDEX_PATTERN_KIBANA = - '.monitoring-kibana-6-*,.monitoring-kibana-7-*' + INDEX_PATTERN_KIBANA_NEW; -export const INDEX_PATTERN_LOGSTASH = - '.monitoring-logstash-6-*,.monitoring-logstash-7-*' + INDEX_PATTERN_LOGSTASH_NEW; -export const INDEX_PATTERN_BEATS = - '.monitoring-beats-6-*,.monitoring-beats-7-*' + INDEX_PATTERN_BEATS_NEW; -export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7' + INDEX_ALERTS_NEW; -export const INDEX_PATTERN_ELASTICSEARCH = - '.monitoring-es-6-*,.monitoring-es-7-*' + INDEX_PATTERN_ELASTICSEARCH_NEW; +export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*'; +export const INDEX_PATTERN_KIBANA = '.monitoring-kibana-6-*,.monitoring-kibana-7-*'; +export const INDEX_PATTERN_LOGSTASH = '.monitoring-logstash-6-*,.monitoring-logstash-7-*'; +export const INDEX_PATTERN_BEATS = '.monitoring-beats-6-*,.monitoring-beats-7-*'; +export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7'; +export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*,.monitoring-es-7-*'; // This is the unique token that exists in monitoring indices collected by metricbeat export const METRICBEAT_INDEX_NAME_UNIQUE_TOKEN = '-mb-'; From 4fd3b45b44fb213c3c1251d3ccbcb471a434caf9 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 27 Feb 2020 20:22:42 -0700 Subject: [PATCH 23/28] fix some missing awaits in functional tests (#58807) --- test/functional/apps/context/_filters.js | 2 +- test/functional/apps/dashboard/panel_expand_toggle.js | 2 +- test/functional/apps/visualize/_tsvb_markdown.ts | 4 ++-- x-pack/test/functional/page_objects/uptime_page.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/functional/apps/context/_filters.js b/test/functional/apps/context/_filters.js index c9499f5a805ab3..721f8a50d0e464 100644 --- a/test/functional/apps/context/_filters.js +++ b/test/functional/apps/context/_filters.js @@ -64,7 +64,7 @@ export default function({ getService, getPageObjects }) { await filterBar.toggleFilterEnabled(TEST_ANCHOR_FILTER_FIELD); await PageObjects.context.waitUntilContextLoadingHasFinished(); - retry.try(async () => { + await retry.try(async () => { expect( await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false) ).to.be(true); diff --git a/test/functional/apps/dashboard/panel_expand_toggle.js b/test/functional/apps/dashboard/panel_expand_toggle.js index 930445a67aa203..5e7d55706968de 100644 --- a/test/functional/apps/dashboard/panel_expand_toggle.js +++ b/test/functional/apps/dashboard/panel_expand_toggle.js @@ -56,7 +56,7 @@ export default function({ getService, getPageObjects }) { // Add a retry to fix https://github.com/elastic/kibana/issues/14574. Perhaps the recent changes to this // being a CSS update is causing the UI to change slower than grabbing the panels? - retry.try(async () => { + await retry.try(async () => { const panelCountAfterMaxThenMinimize = await PageObjects.dashboard.getPanelCount(); expect(panelCountAfterMaxThenMinimize).to.be(panelCount); }); diff --git a/test/functional/apps/visualize/_tsvb_markdown.ts b/test/functional/apps/visualize/_tsvb_markdown.ts index b7307ac9c6cabc..d37404a3d60cb2 100644 --- a/test/functional/apps/visualize/_tsvb_markdown.ts +++ b/test/functional/apps/visualize/_tsvb_markdown.ts @@ -121,7 +121,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.markdownSwitchSubTab('data'); await visualBuilder.cloneSeries(); - retry.try(async function seriesCountCheck() { + await retry.try(async function seriesCountCheck() { const seriesLength = (await visualBuilder.getSeries()).length; expect(seriesLength).to.be.equal(2); }); @@ -131,7 +131,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await visualBuilder.markdownSwitchSubTab('data'); await visualBuilder.createNewAgg(); - retry.try(async function aggregationCountCheck() { + await retry.try(async function aggregationCountCheck() { const aggregationLength = await visualBuilder.getAggregationCount(); expect(aggregationLength).to.be.equal(2); }); diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index a5bd4cc4802873..f6e93cd14e4971 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -57,7 +57,7 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo } public async pageUrlContains(value: string, expected: boolean = true) { - retry.try(async () => { + await retry.try(async () => { expect(await uptimeService.urlContains(value)).to.eql(expected); }); } From 015c7abbca81887d6d2d18c3dc44613d8f9c4c71 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 27 Feb 2020 21:49:33 -0700 Subject: [PATCH 24/28] [Maps] remove getMetricFields from AbstractESSource (#58667) * [Maps] remove getMetricFields from AbstractESSource * do not use formaters for count and unique count * fix jest test --- .../maps/public/layers/fields/es_agg_field.js | 21 +++++++--- .../maps/public/layers/fields/field.js | 8 ++++ .../public/layers/sources/es_agg_source.js | 17 ++++++-- .../maps/public/layers/sources/es_source.js | 42 ++++--------------- .../public/layers/sources/es_term_source.js | 22 ++++------ .../layers/sources/es_term_source.test.js | 2 +- .../maps/public/layers/sources/source.js | 4 +- .../properties/dynamic_color_property.test.js | 3 ++ .../properties/dynamic_style_property.js | 20 +++------ .../tooltips/es_aggmetric_tooltip_property.js | 4 +- .../maps/public/layers/vector_layer.js | 11 +++-- 11 files changed, 72 insertions(+), 82 deletions(-) diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js index 28c199b64d3ef4..27ab8fc5bfb3ae 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js @@ -21,13 +21,17 @@ export class ESAggMetricField extends AbstractField { } getName() { - return this._source.formatMetricKey(this.getAggType(), this.getESDocFieldName()); + return this._source.getAggKey(this.getAggType(), this.getRootName()); + } + + getRootName() { + return this._getESDocFieldName(); } async getLabel() { return this._label - ? await this._label - : this._source.formatMetricLabel(this.getAggType(), this.getESDocFieldName()); + ? this._label + : this._source.getAggLabel(this.getAggType(), this.getRootName()); } getAggType() { @@ -42,13 +46,13 @@ export class ESAggMetricField extends AbstractField { return this.getAggType() === AGG_TYPE.TERMS ? 'string' : 'number'; } - getESDocFieldName() { + _getESDocFieldName() { return this._esDocField ? this._esDocField.getName() : ''; } getRequestDescription() { return this.getAggType() !== AGG_TYPE.COUNT - ? `${this.getAggType()} ${this.getESDocFieldName()}` + ? `${this.getAggType()} ${this.getRootName()}` : AGG_TYPE.COUNT; } @@ -64,7 +68,7 @@ export class ESAggMetricField extends AbstractField { } getValueAggDsl(indexPattern) { - const field = getField(indexPattern, this.getESDocFieldName()); + const field = getField(indexPattern, this.getRootName()); const aggType = this.getAggType(); const aggBody = aggType === AGG_TYPE.TERMS ? { size: 1, shard_size: 1 } : {}; return { @@ -77,6 +81,11 @@ export class ESAggMetricField extends AbstractField { return !isMetricCountable(this.getAggType()); } + canValueBeFormatted() { + // Do not use field formatters for counting metrics + return ![AGG_TYPE.COUNT, AGG_TYPE.UNIQUE_COUNT].includes(this.getAggType()); + } + async getOrdinalFieldMetaRequest(config) { return this._esDocField.getOrdinalFieldMetaRequest(config); } diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/field.js b/x-pack/legacy/plugins/maps/public/layers/fields/field.js index b5d157ad1697aa..2dd553f66755fc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/field.js @@ -17,6 +17,14 @@ export class AbstractField { return this._fieldName; } + getRootName() { + return this.getName(); + } + + canValueBeFormatted() { + return true; + } + getSource() { return this._source; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js index bee35216f59dab..775535d9e2299c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { AbstractESSource } from './es_source'; import { ESAggMetricField } from '../fields/es_agg_field'; import { ESDocField } from '../fields/es_doc_field'; @@ -72,12 +73,22 @@ export class AbstractESAggSource extends AbstractESSource { return metrics; } - formatMetricKey(aggType, fieldName) { + getAggKey(aggType, fieldName) { return aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME; } - formatMetricLabel(aggType, fieldName) { - return aggType !== AGG_TYPE.COUNT ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL; + getAggLabel(aggType, fieldName) { + switch (aggType) { + case AGG_TYPE.COUNT: + return COUNT_PROP_LABEL; + case AGG_TYPE.TERMS: + return i18n.translate('xpack.maps.source.esAggSource.topTermLabel', { + defaultMessage: `Top {fieldName}`, + values: { fieldName }, + }); + default: + return `${aggType} ${fieldName}`; + } } getValueAggsDsl(indexPattern) { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index 5074b218dd615a..f575fd05c80613 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -17,7 +17,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { copyPersistentState } from '../../reducers/util'; -import { ES_GEO_FIELD_TYPE, AGG_TYPE } from '../../../common/constants'; +import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils'; @@ -72,10 +72,6 @@ export class AbstractESSource extends AbstractVectorSource { return clonedDescriptor; } - getMetricFields() { - return []; - } - async _runEsQuery({ requestId, requestName, @@ -254,23 +250,7 @@ export class AbstractESSource extends AbstractVectorSource { return this._descriptor.id; } - async getFieldFormatter(fieldName) { - const metricField = this.getMetricFields().find(field => field.getName() === fieldName); - - // Do not use field formatters for counting metrics - if ( - metricField && - (metricField.type === AGG_TYPE.COUNT || metricField.type === AGG_TYPE.UNIQUE_COUNT) - ) { - return null; - } - - // fieldName could be an aggregation so it needs to be unpacked to expose raw field. - const realFieldName = metricField ? metricField.getESDocFieldName() : fieldName; - if (!realFieldName) { - return null; - } - + async createFieldFormatter(field) { let indexPattern; try { indexPattern = await this.getIndexPattern(); @@ -278,7 +258,7 @@ export class AbstractESSource extends AbstractVectorSource { return null; } - const fieldFromIndexPattern = indexPattern.fields.getByName(realFieldName); + const fieldFromIndexPattern = indexPattern.fields.getByName(field.getRootName()); if (!fieldFromIndexPattern) { return null; } @@ -336,25 +316,19 @@ export class AbstractESSource extends AbstractVectorSource { return resp.aggregations; } - getValueSuggestions = async (fieldName, query) => { - // fieldName could be an aggregation so it needs to be unpacked to expose raw field. - const metricField = this.getMetricFields().find(field => field.getName() === fieldName); - const realFieldName = metricField ? metricField.getESDocFieldName() : fieldName; - if (!realFieldName) { - return []; - } - + getValueSuggestions = async (field, query) => { try { const indexPattern = await this.getIndexPattern(); - const field = indexPattern.fields.getByName(realFieldName); return await autocompleteService.getValueSuggestions({ indexPattern, - field, + field: indexPattern.fields.getByName(field.getRootName()), query, }); } catch (error) { console.warn( - `Unable to fetch suggestions for field: ${fieldName}, query: ${query}, error: ${error.message}` + `Unable to fetch suggestions for field: ${field.getRootName()}, query: ${query}, error: ${ + error.message + }` ); return []; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index 9cc2919404a940..30f60f543d38df 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -67,7 +67,7 @@ export class ESTermSource extends AbstractESAggSource { return this._descriptor.whereQuery; } - formatMetricKey(aggType, fieldName) { + getAggKey(aggType, fieldName) { const metricKey = aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : aggType; return `${FIELD_NAME_PREFIX}${metricKey}${GROUP_BY_DELIMITER}${ @@ -75,21 +75,13 @@ export class ESTermSource extends AbstractESAggSource { }.${this._termField.getName()}`; } - formatMetricLabel(type, fieldName) { - switch (type) { - case AGG_TYPE.COUNT: - return i18n.translate('xpack.maps.source.esJoin.countLabel', { + getAggLabel(aggType, fieldName) { + return aggType === AGG_TYPE.COUNT + ? i18n.translate('xpack.maps.source.esJoin.countLabel', { defaultMessage: `Count of {indexPatternTitle}`, values: { indexPatternTitle: this._descriptor.indexPatternTitle }, - }); - case AGG_TYPE.TERMS: - return i18n.translate('xpack.maps.source.esJoin.topTermLabel', { - defaultMessage: `Top {fieldName}`, - values: { fieldName }, - }); - default: - return `${type} ${fieldName}`; - } + }) + : super.getAggLabel(aggType, fieldName); } async getPropertiesMap(searchFilters, leftSourceName, leftFieldName, registerCancelCallback) { @@ -116,7 +108,7 @@ export class ESTermSource extends AbstractESAggSource { requestDescription: this._getRequestDescription(leftSourceName, leftFieldName), }); - const countPropertyName = this.formatMetricKey(AGG_TYPE.COUNT); + const countPropertyName = this.getAggKey(AGG_TYPE.COUNT); return { propertiesMap: extractPropertiesMap(rawEsData, countPropertyName), }; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js index 39cc301d458cbe..d6f9f6d2911e91 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js @@ -54,7 +54,7 @@ describe('getMetricFields', () => { expect(metrics.length).toBe(2); expect(metrics[0].getAggType()).toEqual('sum'); - expect(metrics[0].getESDocFieldName()).toEqual(sumFieldName); + expect(metrics[0].getRootName()).toEqual(sumFieldName); expect(metrics[0].getName()).toEqual( '__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField' ); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js index 3c6ddb74bedeba..4fef52e731f9b2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js @@ -132,7 +132,7 @@ export class AbstractSource { } // Returns function used to format value - async getFieldFormatter(/* fieldName */) { + async createFieldFormatter(/* field */) { return null; } @@ -140,7 +140,7 @@ export class AbstractSource { throw new Error(`Source#loadStylePropsMeta not implemented`); } - async getValueSuggestions(/* fieldName, query */) { + async getValueSuggestions(/* field, query */) { return []; } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js index 6b08fc2a105c3a..8648b073a7b79a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js @@ -25,6 +25,9 @@ const mockField = { getName() { return 'foobar'; }, + getRootName() { + return 'foobar'; + }, supportsFieldMeta() { return true; }, diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js index af78c4c0e461e8..e40c82e6276c7e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js @@ -13,7 +13,6 @@ import React from 'react'; import { OrdinalLegend } from './components/ordinal_legend'; import { CategoricalLegend } from './components/categorical_legend'; import { OrdinalFieldMetaOptionsPopover } from '../components/ordinal_field_meta_options_popover'; -import { ESAggMetricField } from '../../../fields/es_agg_field'; export class DynamicStyleProperty extends AbstractStyleProperty { static type = STYLE_TYPE.DYNAMIC; @@ -26,9 +25,9 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } getValueSuggestions = query => { - const fieldName = this.getFieldName(); + const field = this.getField(); const fieldSource = this.getFieldSource(); - return fieldSource && fieldName ? fieldSource.getValueSuggestions(fieldName, query) : []; + return fieldSource && field ? fieldSource.getValueSuggestions(field, query) : []; }; getFieldMeta() { @@ -185,11 +184,7 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } _pluckOrdinalStyleMetaFromFieldMetaData(fieldMetaData) { - const realFieldName = - this._field instanceof ESAggMetricField - ? this._field.getESDocFieldName() - : this._field.getName(); - const stats = fieldMetaData[realFieldName]; + const stats = fieldMetaData[this._field.getRootName()]; if (!stats) { return null; } @@ -209,15 +204,12 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } _pluckCategoricalStyleMetaFromFieldMetaData(fieldMetaData) { - const realFieldName = - this._field instanceof ESAggMetricField - ? this._field.getESDocFieldName() - : this._field.getName(); - if (!fieldMetaData[realFieldName] || !fieldMetaData[realFieldName].buckets) { + const rootFieldName = this._field.getRootName(); + if (!fieldMetaData[rootFieldName] || !fieldMetaData[rootFieldName].buckets) { return null; } - const ordered = fieldMetaData[realFieldName].buckets.map(bucket => { + const ordered = fieldMetaData[rootFieldName].buckets.map(bucket => { return { key: bucket.key, count: bucket.doc_count, diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js index 229c84fe234bd9..ea000a78331eb7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js @@ -27,9 +27,7 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty { ) { return this._rawValue; } - const indexPatternField = this._indexPattern.fields.getByName( - this._metricField.getESDocFieldName() - ); + const indexPatternField = this._indexPattern.fields.getByName(this._metricField.getRootName()); if (!indexPatternField) { return this._rawValue; } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index e1a30c8aef1d37..c515feecc15513 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -561,10 +561,13 @@ export class VectorLayer extends AbstractLayer { startLoading(dataRequestId, requestToken, nextMeta); const formatters = {}; - const promises = fields.map(async field => { - const fieldName = field.getName(); - formatters[fieldName] = await source.getFieldFormatter(fieldName); - }); + const promises = fields + .filter(field => { + return field.canValueBeFormatted(); + }) + .map(async field => { + formatters[field.getName()] = await source.createFieldFormatter(field); + }); await Promise.all(promises); stopLoading(dataRequestId, requestToken, formatters, nextMeta); From 511a9c2bee83890a874c124f30231482e6a5773e Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 27 Feb 2020 22:18:00 -0700 Subject: [PATCH 25/28] Revert "Prep agg types for new platform (#57064)" This reverts commit 0cede6a705db65ef450426db3c584fcabab42780. --- packages/kbn-utility-types/README.md | 1 - packages/kbn-utility-types/index.ts | 2 +- .../filters/brush_event.test.mocks.ts} | 13 +- .../actions/filters/brush_event.test.ts | 49 +- src/legacy/core_plugins/data/public/index.ts | 12 +- src/legacy/core_plugins/data/public/plugin.ts | 6 +- .../public/search/aggs/agg_config.test.ts | 497 ------ .../data/public/search/aggs/agg_config.ts | 61 +- .../public/search/aggs/agg_configs.test.ts | 503 ------ .../data/public/search/aggs/agg_configs.ts | 76 +- .../public/search/aggs/agg_params.test.ts | 2 + .../data/public/search/aggs/agg_type.test.ts | 16 +- .../data/public/search/aggs/agg_type.ts | 5 +- .../search/aggs/agg_types_registry.test.ts | 91 -- .../public/search/aggs/agg_types_registry.ts | 68 - .../search/aggs/buckets/_bucket_agg_type.ts | 12 +- .../search/aggs/buckets/_interval_options.ts | 1 - .../create_filter/date_histogram.test.ts | 12 +- .../buckets/create_filter/date_range.test.ts | 7 +- .../buckets/create_filter/filters.test.ts | 13 +- .../buckets/create_filter/histogram.test.ts | 12 +- .../buckets/create_filter/ip_range.test.ts | 11 +- .../aggs/buckets/create_filter/range.test.ts | 12 +- .../aggs/buckets/create_filter/terms.test.ts | 11 +- .../search/aggs/buckets/date_histogram.ts | 8 +- .../search/aggs/buckets/date_range.test.ts | 25 +- .../public/search/aggs/buckets/date_range.ts | 12 +- .../data/public/search/aggs/buckets/filter.ts | 1 - .../public/search/aggs/buckets/filters.ts | 26 +- .../search/aggs/buckets/geo_hash.test.ts | 7 +- .../public/search/aggs/buckets/geo_tile.ts | 3 +- .../search/aggs/buckets/histogram.test.ts | 33 +- .../public/search/aggs/buckets/histogram.ts | 12 +- .../public/search/aggs/buckets/ip_range.ts | 12 +- .../buckets/migrate_include_exclude_format.ts | 4 +- .../public/search/aggs/buckets/range.test.ts | 12 +- .../aggs/buckets/significant_terms.test.ts | 9 +- .../public/search/aggs/buckets/terms.test.ts | 8 +- .../aggs/filter/agg_type_filters.test.ts | 5 +- .../search/aggs/filter/agg_type_filters.ts | 1 - .../search/aggs/filter/prop_filter.test.ts | 19 +- .../data/public/search/aggs/index.test.ts | 2 + .../data/public/search/aggs/index.ts | 9 +- .../public/search/aggs/metrics/bucket_avg.ts | 1 + .../public/search/aggs/metrics/bucket_max.ts | 1 + .../public/search/aggs/metrics/bucket_min.ts | 1 - .../public/search/aggs/metrics/cardinality.ts | 5 +- .../data/public/search/aggs/metrics/count.ts | 7 +- .../lib/get_response_agg_config_class.ts | 1 - .../metrics/lib/parent_pipeline_agg_helper.ts | 1 + .../lib/sibling_pipeline_agg_helper.ts | 1 + .../public/search/aggs/metrics/median.test.ts | 7 +- .../data/public/search/aggs/metrics/median.ts | 4 +- .../search/aggs/metrics/metric_agg_type.ts | 7 +- .../data/public/search/aggs/metrics/min.ts | 1 - .../aggs/metrics/parent_pipeline.test.ts | 18 +- .../aggs/metrics/percentile_ranks.test.ts | 8 +- .../search/aggs/metrics/percentile_ranks.ts | 7 +- .../search/aggs/metrics/percentiles.test.ts | 6 +- .../public/search/aggs/metrics/percentiles.ts | 4 + .../aggs/metrics/sibling_pipeline.test.ts | 22 +- .../search/aggs/metrics/std_deviation.test.ts | 6 +- .../search/aggs/metrics/top_hit.test.ts | 6 +- .../public/search/aggs/param_types/agg.ts | 4 +- .../public/search/aggs/param_types/base.ts | 4 +- .../search/aggs/param_types/field.test.ts | 2 + .../public/search/aggs/param_types/field.ts | 5 +- .../param_types/filter/field_filters.test.ts | 11 +- .../aggs/param_types/filter/field_filters.ts | 8 +- .../search/aggs/param_types/json.test.ts | 8 +- .../public/search/aggs/param_types/json.ts | 4 +- .../search/aggs/param_types/optioned.test.ts | 2 + .../search/aggs/param_types/optioned.ts | 6 +- .../search/aggs/param_types/string.test.ts | 8 +- .../public/search/aggs/param_types/string.ts | 4 +- .../test_helpers/mock_agg_types_registry.ts | 57 - .../aggs/test_helpers/mock_data_services.ts | 54 - .../data/public/search/aggs/types.ts | 2 +- .../data/public/search/aggs/utils.test.tsx | 2 + .../data/public/search/aggs/utils.ts | 39 +- .../data/public/search/expressions/esaggs.ts | 4 +- .../data/public/search/expressions/utils.ts | 5 +- .../core_plugins/data/public/search/mocks.ts | 85 - .../data/public/search/search_service.ts | 60 +- .../data/public/search/tabify/buckets.test.ts | 2 + .../public/search/tabify/get_columns.test.ts | 22 +- .../search/tabify/response_writer.test.ts | 20 +- .../data/public/search/tabify/tabify.test.ts | 16 +- .../components/sidebar/state/reducers.ts | 18 +- .../public/legacy_imports.ts | 2 +- .../public/table_vis_controller.test.ts | 4 +- .../visualizations/public/legacy_imports.ts | 2 +- .../public/np_ready/public/vis_impl.js | 6 +- src/legacy/ui/public/agg_types/index.ts | 9 +- .../ui/public/vis/__tests__/_agg_config.js | 485 ++++++ .../ui/public/vis/__tests__/_agg_configs.js | 420 +++++ .../public/vis/__tests__/index.js} | 4 +- .../data/common/field_formats/mocks.ts | 49 - src/plugins/data/public/mocks.ts | 28 +- .../data/public/search/search_source/mocks.ts | 19 + .../editor_frame_service/service.test.tsx | 4 + .../__snapshots__/zeek_details.test.tsx.snap | 1448 +++++++++-------- 102 files changed, 2101 insertions(+), 2646 deletions(-) rename src/legacy/core_plugins/data/public/{services.ts => actions/filters/brush_event.test.mocks.ts} (76%) delete mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts delete mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts delete mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts delete mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts delete mode 100644 src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts delete mode 100644 src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts delete mode 100644 src/legacy/core_plugins/data/public/search/mocks.ts create mode 100644 src/legacy/ui/public/vis/__tests__/_agg_config.js create mode 100644 src/legacy/ui/public/vis/__tests__/_agg_configs.js rename src/legacy/{core_plugins/data/public/search/aggs/test_helpers/index.ts => ui/public/vis/__tests__/index.js} (86%) delete mode 100644 src/plugins/data/common/field_formats/mocks.ts diff --git a/packages/kbn-utility-types/README.md b/packages/kbn-utility-types/README.md index b57e98e379707e..829fd21e143669 100644 --- a/packages/kbn-utility-types/README.md +++ b/packages/kbn-utility-types/README.md @@ -18,7 +18,6 @@ type B = UnwrapPromise
; // string ## Reference -- `Assign` — From `U` assign properties to `T` (just like object assign). - `Ensure` — Makes sure `T` is of type `X`. - `ObservableLike` — Minimal interface for an object resembling an `Observable`. - `PublicContract` — Returns an object with public keys only. diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 657d9f547de66e..808935ed4cb5b8 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -18,7 +18,7 @@ */ import { PromiseType } from 'utility-types'; -export { $Values, Assign, Class, Optional, Required } from 'utility-types'; +export { $Values, Required, Optional, Class } from 'utility-types'; /** * A type that may or may not be a `Promise`. diff --git a/src/legacy/core_plugins/data/public/services.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts similarity index 76% rename from src/legacy/core_plugins/data/public/services.ts rename to src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts index 7ecd041c70e22e..2cecfd0fe8b767 100644 --- a/src/legacy/core_plugins/data/public/services.ts +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts @@ -17,9 +17,12 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -import { SearchStart } from './search/search_service'; +import { chromeServiceMock } from '../../../../../../core/public/mocks'; -export const [getSearchServiceShim, setSearchServiceShim] = createGetterSetter( - 'searchShim' -); +jest.doMock('ui/new_platform', () => ({ + npStart: { + core: { + chrome: chromeServiceMock.createStartContract(), + }, + }, +})); diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts index eb29530f92fee7..0e18c7c707fa3f 100644 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts @@ -19,13 +19,33 @@ import moment from 'moment'; -import { onBrushEvent, BrushEvent } from './brush_event'; +jest.mock('../../search/aggs', () => ({ + AggConfigs: function AggConfigs() { + return { + createAggConfig: ({ params }: Record) => ({ + params, + getIndexPattern: () => ({ + timeFieldName: 'time', + }), + }), + }; + }, +})); + +jest.mock('../../../../../../plugins/data/public/services', () => ({ + getIndexPatterns: () => { + return { + get: async () => { + return { + id: 'logstash-*', + timeFieldName: 'time', + }; + }, + }; + }, +})); -import { mockDataServices } from '../../search/aggs/test_helpers'; -import { IndexPatternsContract } from '../../../../../../plugins/data/public'; -import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setIndexPatterns } from '../../../../../../plugins/data/public/services'; +import { onBrushEvent, BrushEvent } from './brush_event'; describe('brushEvent', () => { const DAY_IN_MS = 24 * 60 * 60 * 1000; @@ -39,28 +59,11 @@ describe('brushEvent', () => { }, getIndexPattern: () => ({ timeFieldName: 'time', - fields: { - getByName: () => undefined, - filter: () => [], - }, }), }, ]; beforeEach(() => { - mockDataServices(); - setIndexPatterns(({ - ...dataPluginMock.createStartContract().indexPatterns, - get: async () => ({ - id: 'indexPatternId', - timeFieldName: 'time', - fields: { - getByName: () => undefined, - filter: () => [], - }, - }), - } as unknown) as IndexPatternsContract); - baseEvent = { data: { ordered: { diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 8d730d18a17559..8cde5d0a1fc115 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -35,18 +35,18 @@ export { } from '../../../../plugins/data/public'; export { // agg_types - AggParam, // only the type is used externally, only in vis editor - AggParamOption, // only the type is used externally - DateRangeKey, // only used in field formatter deserialization, which will live in data + AggParam, + AggParamOption, + DateRangeKey, IAggConfig, IAggConfigs, IAggType, IFieldParamType, IMetricAggType, - IpRangeKey, // only used in field formatter deserialization, which will live in data + IpRangeKey, ISchemas, - OptionedParamEditorProps, // only type is used externally - OptionedValueProp, // only type is used externally + OptionedParamEditorProps, + OptionedValueProp, } from './search/types'; /** @public static code */ diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index e2b8ca5dda78cf..e13e8e34eaebec 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -36,7 +36,6 @@ import { setOverlays, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../plugins/data/public/services'; -import { setSearchServiceShim } from './services'; import { SELECT_RANGE_ACTION, selectRangeAction } from './actions/select_range_action'; import { VALUE_CLICK_ACTION, valueClickAction } from './actions/value_click_action'; import { @@ -113,9 +112,6 @@ export class DataPlugin } public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { - const search = this.search.start(core); - setSearchServiceShim(search); - setUiSettings(core.uiSettings); setQueryService(data.query); setIndexPatterns(data.indexPatterns); @@ -127,7 +123,7 @@ export class DataPlugin uiActions.attachAction(VALUE_CLICK_TRIGGER, VALUE_CLICK_ACTION); return { - search, + search: this.search.start(core), }; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts deleted file mode 100644 index 7769aa29184d3a..00000000000000 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { identity } from 'lodash'; - -import { AggConfig, IAggConfig } from './agg_config'; -import { AggConfigs, CreateAggConfigParams } from './agg_configs'; -import { AggType } from './agg_types'; -import { AggTypesRegistryStart } from './agg_types_registry'; -import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; -import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { stubIndexPatternWithFields } from '../../../../../../plugins/data/public/stubs'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setFieldFormats } from '../../../../../../plugins/data/public/services'; - -describe('AggConfig', () => { - let indexPattern: IndexPattern; - let typesRegistry: AggTypesRegistryStart; - - beforeEach(() => { - jest.restoreAllMocks(); - mockDataServices(); - indexPattern = stubIndexPatternWithFields as IndexPattern; - typesRegistry = mockAggTypesRegistry(); - }); - - describe('#toDsl', () => { - it('calls #write()', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - const configStates = { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }; - const aggConfig = ac.createAggConfig(configStates); - - const spy = jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: {} })); - aggConfig.toDsl(); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('uses the type name as the agg name', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - const configStates = { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }; - const aggConfig = ac.createAggConfig(configStates); - - jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: {} })); - const dsl = aggConfig.toDsl(); - expect(dsl).toHaveProperty('date_histogram'); - }); - - it('uses the params from #write() output as the agg params', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - const configStates = { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }; - const aggConfig = ac.createAggConfig(configStates); - - const football = {}; - jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: football })); - const dsl = aggConfig.toDsl(); - expect(dsl.date_histogram).toBe(football); - }); - - it('includes subAggs from #write() output', () => { - const configStates = [ - { - enabled: true, - type: 'avg', - schema: 'metric', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - - const histoConfig = ac.byName('date_histogram')[0]; - const avgConfig = ac.byName('avg')[0]; - const football = {}; - - jest - .spyOn(histoConfig, 'write') - .mockImplementation(() => ({ params: {}, subAggs: [avgConfig] })); - jest.spyOn(avgConfig, 'write').mockImplementation(() => ({ params: football })); - - const dsl = histoConfig.toDsl(); - expect(dsl).toHaveProperty('aggs'); - expect(dsl.aggs).toHaveProperty(avgConfig.id); - expect(dsl.aggs[avgConfig.id]).toHaveProperty('avg'); - expect(dsl.aggs[avgConfig.id].avg).toBe(football); - }); - }); - - describe('::ensureIds', () => { - it('accepts an array of objects and assigns ids to them', () => { - const objs = [{}, {}, {}, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).toHaveProperty('id', '1'); - expect(objs[1]).toHaveProperty('id', '2'); - expect(objs[2]).toHaveProperty('id', '3'); - expect(objs[3]).toHaveProperty('id', '4'); - }); - - it('assigns ids relative to the other only item in the list', () => { - const objs = [{ id: '100' }, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).toHaveProperty('id', '100'); - expect(objs[1]).toHaveProperty('id', '101'); - }); - - it('assigns ids relative to the other items in the list', () => { - const objs = [{ id: '100' }, { id: '200' }, { id: '500' }, { id: '350' }, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).toHaveProperty('id', '100'); - expect(objs[1]).toHaveProperty('id', '200'); - expect(objs[2]).toHaveProperty('id', '500'); - expect(objs[3]).toHaveProperty('id', '350'); - expect(objs[4]).toHaveProperty('id', '501'); - }); - - it('uses ::nextId to get the starting value', () => { - jest.spyOn(AggConfig, 'nextId').mockImplementation(() => 534); - const objs = AggConfig.ensureIds([{}]); - expect(objs[0]).toHaveProperty('id', '534'); - }); - - it('only calls ::nextId once', () => { - const start = 420; - const spy = jest.spyOn(AggConfig, 'nextId').mockImplementation(() => start); - const objs = AggConfig.ensureIds([{}, {}, {}, {}, {}, {}, {}]); - - expect(spy).toHaveBeenCalledTimes(1); - objs.forEach((obj, i) => { - expect(obj).toHaveProperty('id', String(start + i)); - }); - }); - }); - - describe('::nextId', () => { - it('accepts a list of objects and picks the next id', () => { - const next = AggConfig.nextId([{ id: '100' }, { id: '500' }] as IAggConfig[]); - expect(next).toBe(501); - }); - - it('handles an empty list', () => { - const next = AggConfig.nextId([]); - expect(next).toBe(1); - }); - - it('fails when the list is not defined', () => { - expect(() => { - AggConfig.nextId((undefined as unknown) as IAggConfig[]); - }).toThrowError(); - }); - }); - - describe('#toJsonDataEquals', () => { - const testsIdentical = [ - [ - { - enabled: true, - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - [ - { - enabled: true, - type: 'avg', - schema: 'metric', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ], - ]; - - testsIdentical.forEach((configState, index) => { - it(`identical aggregations (${index})`, () => { - const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry }); - expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); - }); - }); - - const testsIdenticalDifferentOrder = [ - { - config1: [ - { - enabled: true, - type: 'avg', - schema: 'metric', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ], - config2: [ - { - enabled: true, - schema: 'metric', - type: 'avg', - params: {}, - }, - { - enabled: true, - schema: 'segment', - type: 'date_histogram', - params: {}, - }, - ], - }, - ]; - - testsIdenticalDifferentOrder.forEach((test, index) => { - it(`identical aggregations (${index}) - init json is in different order`, () => { - const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); - expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); - }); - }); - - const testsDifferent = [ - { - config1: [ - { - enabled: true, - type: 'avg', - schema: 'metric', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ], - config2: [ - { - enabled: true, - type: 'max', - schema: 'metric', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ], - }, - { - config1: [ - { - enabled: true, - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - config2: [ - { - enabled: true, - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ], - }, - ]; - - testsDifferent.forEach((test, index) => { - it(`different aggregations (${index})`, () => { - const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); - expect(ac1.jsonDataEquals(ac2.aggs)).toBe(false); - }); - }); - }); - - describe('#toJSON', () => { - it('includes the aggs id, params, type and schema', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - const configStates = { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }; - const aggConfig = ac.createAggConfig(configStates); - - expect(aggConfig.id).toBe('1'); - expect(typeof aggConfig.params).toBe('object'); - expect(aggConfig.type).toBeInstanceOf(AggType); - expect(aggConfig.type).toHaveProperty('name', 'date_histogram'); - expect(typeof aggConfig.schema).toBe('object'); - expect(aggConfig.schema).toHaveProperty('name', 'segment'); - - const state = aggConfig.toJSON(); - expect(state).toHaveProperty('id', '1'); - expect(typeof state.params).toBe('object'); - expect(state).toHaveProperty('type', 'date_histogram'); - expect(state).toHaveProperty('schema', 'segment'); - }); - - it('test serialization order is identical (for visual consistency)', () => { - const configStates = [ - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: {}, - }, - ]; - const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry }); - - // this relies on the assumption that js-engines consistently loop over properties in insertion order. - // most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. - expect(JSON.stringify(ac1.aggs) === JSON.stringify(ac2.aggs)).toBe(true); - }); - }); - - describe('#makeLabel', () => { - let aggConfig: AggConfig; - - beforeEach(() => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - aggConfig = ac.createAggConfig({ type: 'count' } as CreateAggConfigParams); - }); - - it('uses the custom label if it is defined', () => { - aggConfig.params.customLabel = 'Custom label'; - const label = aggConfig.makeLabel(); - expect(label).toBe(aggConfig.params.customLabel); - }); - - it('default label should be "Count"', () => { - const label = aggConfig.makeLabel(); - expect(label).toBe('Count'); - }); - - it('default label should be "Percentage of Count" when percentageMode is set to true', () => { - const label = aggConfig.makeLabel(true); - expect(label).toBe('Percentage of Count'); - }); - - it('empty label if the type is not defined', () => { - aggConfig.type = (undefined as unknown) as AggType; - const label = aggConfig.makeLabel(); - expect(label).toBe(''); - }); - }); - - describe('#fieldFormatter - custom getFormat handler', () => { - it('returns formatter from getFormat handler', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), - })) as any, - }); - - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); - const configStates = { - enabled: true, - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }; - const aggConfig = ac.createAggConfig(configStates); - - const fieldFormatter = aggConfig.fieldFormatter(); - expect(fieldFormatter).toBeDefined(); - expect(fieldFormatter('text')).toBe('text'); - }); - }); - - // TODO: Converting these field formatter tests from browser tests to unit - // tests makes them much less helpful due to the extensive use of mocking. - // We should revisit these and rewrite them into something more useful. - describe('#fieldFormatter - no custom getFormat handler', () => { - let aggConfig: AggConfig; - - beforeEach(() => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: (t?: string) => t || identity, - })) as any, - }); - indexPattern.fields.getByName = name => - ({ - format: { - getConverterFor: (t?: string) => t || identity, - }, - } as IndexPatternField); - - const configStates = { - enabled: true, - type: 'histogram', - schema: 'bucket', - params: { - field: { - format: { - getConverterFor: (t?: string) => t || identity, - }, - }, - }, - }; - const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry }); - aggConfig = ac.createAggConfig(configStates); - }); - - it("returns the field's formatter", () => { - expect(aggConfig.fieldFormatter().toString()).toBe( - aggConfig - .getField() - .format.getConverterFor() - .toString() - ); - }); - - it('returns the string format if the field does not have a format', () => { - const agg = aggConfig; - agg.params.field = { type: 'number', format: null }; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).toBeDefined(); - expect(fieldFormatter('text')).toBe('text'); - }); - - it('returns the string format if there is no field', () => { - const agg = aggConfig; - delete agg.params.field; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).toBeDefined(); - expect(fieldFormatter('text')).toBe('text'); - }); - - it('returns the html converter if "html" is passed in', () => { - const field = indexPattern.fields.getByName('bytes'); - expect(aggConfig.fieldFormatter('html').toString()).toBe( - field!.format.getConverterFor('html').toString() - ); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts index 659bec3f702e37..2b21c5c4868a52 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts @@ -17,8 +17,16 @@ * under the License. */ +/** + * @name AggConfig + * + * @description This class represents an aggregation, which is displayed in the left-hand nav of + * the Visualize app. + */ + import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { IAggType } from './agg_type'; import { AggGroupNames } from './agg_groups'; import { writeParams } from './agg_params'; @@ -30,20 +38,18 @@ import { FieldFormatsContentType, KBN_FIELD_TYPES, } from '../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../plugins/data/public/services'; export interface AggConfigOptions { - type: IAggType; - enabled?: boolean; + enabled: boolean; + type: string; + params: any; id?: string; - params?: Record; - schema?: string | Schema; + schema?: string; } const unknownSchema: Schema = { name: 'unknown', - title: 'Unknown', // only here for illustrative purposes + title: 'Unknown', hideCustomLabel: true, aggFilter: [], min: 1, @@ -59,6 +65,21 @@ const unknownSchema: Schema = { }, }; +const getTypeFromRegistry = (type: string): IAggType => { + // We need to inline require here, since we're having a cyclic dependency + // from somewhere inside agg_types back to AggConfig. + const aggTypes = require('../aggs').aggTypes; + const registeredType = + aggTypes.metrics.find((agg: IAggType) => agg.name === type) || + aggTypes.buckets.find((agg: IAggType) => agg.name === type); + + if (!registeredType) { + throw new Error('unknown type'); + } + + return registeredType; +}; + const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { let registeredSchema = schemas ? schemas.byName[schema] : null; if (!registeredSchema) { @@ -69,13 +90,6 @@ const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { return registeredSchema; }; -/** - * @name AggConfig - * - * @description This class represents an aggregation, which is displayed in the left-hand nav of - * the Visualize app. - */ - // TODO need to make a more explicit interface for this export type IAggConfig = AggConfig; @@ -87,9 +101,9 @@ export class AggConfig { * @param {array[object]} list - a list of objects, objects can be anything really * @return {array} - the list that was passed in */ - static ensureIds(list: any[]) { - const have: IAggConfig[] = []; - const haveNot: AggConfigOptions[] = []; + static ensureIds(list: AggConfig[]) { + const have: AggConfig[] = []; + const haveNot: AggConfig[] = []; list.forEach(function(obj) { (obj.id ? have : haveNot).push(obj); }); @@ -107,7 +121,7 @@ export class AggConfig { * * @return {array} list - a list of objects with id properties */ - static nextId(list: IAggConfig[]) { + static nextId(list: AggConfig[]) { return ( 1 + list.reduce(function(max, obj) { @@ -147,10 +161,10 @@ export class AggConfig { // set the params to the values from opts, or just to the defaults this.setParams(opts.params || {}); - // @ts-ignore - this.__schema = this.__schema; // @ts-ignore this.__type = this.__type; + // @ts-ignore + this.__schema = this.__schema; } /** @@ -380,8 +394,7 @@ export class AggConfig { } fieldOwnFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { - const fieldFormatsService = getFieldFormats(); - + const fieldFormatsService = npStart.plugins.data.fieldFormats; const field = this.getField(); let format = field && field.format; if (!format) format = defaultFormat; @@ -443,8 +456,8 @@ export class AggConfig { }); } - public setType(type: IAggType) { - this.type = type; + public setType(type: string | IAggType) { + this.type = typeof type === 'string' ? getTypeFromRegistry(type) : type; } public get schema() { diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts deleted file mode 100644 index 29f16b1e4f0bf2..00000000000000 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { indexBy } from 'lodash'; -import { AggConfig } from './agg_config'; -import { AggConfigs } from './agg_configs'; -import { AggTypesRegistryStart } from './agg_types_registry'; -import { Schemas } from './schemas'; -import { AggGroupNames } from './agg_groups'; -import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; -import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; -import { - stubIndexPattern, - stubIndexPatternWithFields, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../plugins/data/public/stubs'; - -describe('AggConfigs', () => { - let indexPattern: IndexPattern; - let typesRegistry: AggTypesRegistryStart; - - beforeEach(() => { - indexPattern = stubIndexPatternWithFields as IndexPattern; - typesRegistry = mockAggTypesRegistry(); - }); - - describe('constructor', () => { - it('handles passing just a type', () => { - const configStates = [ - { - enabled: true, - type: 'histogram', - params: {}, - }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - expect(ac.aggs).toHaveLength(1); - }); - - it('attempts to ensure that all states have an id', () => { - const configStates = [ - { - enabled: true, - type: 'histogram', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - params: {}, - }, - { - enabled: true, - type: 'terms', - params: {}, - schema: 'split', - }, - ]; - - const spy = jest.spyOn(AggConfig, 'ensureIds'); - new AggConfigs(indexPattern, configStates, { typesRegistry }); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy.mock.calls[0]).toEqual([configStates]); - spy.mockRestore(); - }); - - describe('defaults', () => { - const schemas = new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: 'Simple', - min: 1, - max: 2, - defaults: [ - { schema: 'metric', type: 'count' }, - { schema: 'metric', type: 'avg' }, - { schema: 'metric', type: 'sum' }, - ], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: 'Example', - min: 0, - max: 1, - defaults: [ - { schema: 'segment', type: 'terms' }, - { schema: 'segment', type: 'filters' }, - ], - }, - ]); - - it('should only set the number of defaults defined by the max', () => { - const ac = new AggConfigs(indexPattern, [], { - schemas: schemas.all, - typesRegistry, - }); - expect(ac.bySchemaName('metric')).toHaveLength(2); - }); - - it('should set the defaults defined in the schema when none exist', () => { - const ac = new AggConfigs(indexPattern, [], { - schemas: schemas.all, - typesRegistry, - }); - expect(ac.aggs).toHaveLength(3); - }); - - it('should NOT set the defaults defined in the schema when some exist', () => { - const configStates = [ - { - enabled: true, - type: 'date_histogram', - params: {}, - schema: 'segment', - }, - ]; - const ac = new AggConfigs(indexPattern, configStates, { - schemas: schemas.all, - typesRegistry, - }); - expect(ac.aggs).toHaveLength(3); - expect(ac.bySchemaName('segment')[0].type.name).toEqual('date_histogram'); - }); - }); - }); - - describe('#createAggConfig', () => { - it('accepts a configState which is provided as an AggConfig object', () => { - const configStates = [ - { - enabled: true, - type: 'histogram', - params: {}, - }, - { - enabled: true, - type: 'date_histogram', - params: {}, - }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - expect(ac.aggs).toHaveLength(2); - - ac.createAggConfig( - new AggConfig(ac, { - enabled: true, - type: typesRegistry.get('terms'), - params: {}, - schema: 'split', - }) - ); - expect(ac.aggs).toHaveLength(3); - }); - - it('adds new AggConfig entries to AggConfigs by default', () => { - const configStates = [ - { - enabled: true, - type: 'histogram', - params: {}, - }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - expect(ac.aggs).toHaveLength(1); - - ac.createAggConfig({ - enabled: true, - type: 'terms', - params: {}, - schema: 'split', - }); - expect(ac.aggs).toHaveLength(2); - }); - - it('does not add an agg to AggConfigs if addToAggConfigs: false', () => { - const configStates = [ - { - enabled: true, - type: 'histogram', - params: {}, - }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - expect(ac.aggs).toHaveLength(1); - - ac.createAggConfig( - { - enabled: true, - type: 'terms', - params: {}, - schema: 'split', - }, - { addToAggConfigs: false } - ); - expect(ac.aggs).toHaveLength(1); - }); - }); - - describe('#getRequestAggs', () => { - it('performs a stable sort, but moves metrics to the bottom', () => { - const configStates = [ - { type: 'avg', enabled: true, params: {}, schema: 'metric' }, - { type: 'terms', enabled: true, params: {}, schema: 'split' }, - { type: 'histogram', enabled: true, params: {}, schema: 'split' }, - { type: 'sum', enabled: true, params: {}, schema: 'metric' }, - { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, - { type: 'filters', enabled: true, params: {}, schema: 'split' }, - { type: 'percentiles', enabled: true, params: {}, schema: 'metric' }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const sorted = ac.getRequestAggs(); - const aggs = indexBy(ac.aggs, agg => agg.type.name); - - expect(sorted.shift()).toBe(aggs.terms); - expect(sorted.shift()).toBe(aggs.histogram); - expect(sorted.shift()).toBe(aggs.date_histogram); - expect(sorted.shift()).toBe(aggs.filters); - expect(sorted.shift()).toBe(aggs.avg); - expect(sorted.shift()).toBe(aggs.sum); - expect(sorted.shift()).toBe(aggs.percentiles); - expect(sorted).toHaveLength(0); - }); - }); - - describe('#getResponseAggs', () => { - it('returns all request aggs for basic aggs', () => { - const configStates = [ - { type: 'terms', enabled: true, params: {}, schema: 'split' }, - { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, - { type: 'count', enabled: true, params: {}, schema: 'metric' }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const sorted = ac.getResponseAggs(); - const aggs = indexBy(ac.aggs, agg => agg.type.name); - - expect(sorted.shift()).toBe(aggs.terms); - expect(sorted.shift()).toBe(aggs.date_histogram); - expect(sorted.shift()).toBe(aggs.count); - expect(sorted).toHaveLength(0); - }); - - it('expands aggs that have multiple responses', () => { - const configStates = [ - { type: 'terms', enabled: true, params: {}, schema: 'split' }, - { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, - { type: 'percentiles', enabled: true, params: { percents: [1, 2, 3] }, schema: 'metric' }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const sorted = ac.getResponseAggs(); - const aggs = indexBy(ac.aggs, agg => agg.type.name); - - expect(sorted.shift()).toBe(aggs.terms); - expect(sorted.shift()).toBe(aggs.date_histogram); - expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 1); - expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 2); - expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 3); - expect(sorted).toHaveLength(0); - }); - }); - - describe('#toDsl', () => { - const schemas = new Schemas([ - { - group: AggGroupNames.Buckets, - name: 'segment', - }, - { - group: AggGroupNames.Buckets, - name: 'split', - }, - ]); - - beforeEach(() => { - mockDataServices(); - indexPattern = stubIndexPattern as IndexPattern; - indexPattern.fields.getByName = name => (name as unknown) as IndexPatternField; - }); - - it('uses the sorted aggs', () => { - const configStates = [{ enabled: true, type: 'avg', params: { field: 'bytes' } }]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const spy = jest.spyOn(AggConfigs.prototype, 'getRequestAggs'); - ac.toDsl(); - expect(spy).toHaveBeenCalledTimes(1); - spy.mockRestore(); - }); - - it('calls aggConfig#toDsl() on each aggConfig and compiles the nested output', () => { - const configStates = [ - { enabled: true, type: 'date_histogram', params: {}, schema: 'segment' }, - { enabled: true, type: 'terms', params: {}, schema: 'split' }, - { enabled: true, type: 'count', params: {} }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { - typesRegistry, - schemas: schemas.all, - }); - - const aggInfos = ac.aggs.map(aggConfig => { - const football = {}; - aggConfig.toDsl = jest.fn().mockImplementation(() => football); - - return { - id: aggConfig.id, - football, - }; - }); - - (function recurse(lvl: Record): void { - const info = aggInfos.shift(); - if (!info) return; - - expect(lvl).toHaveProperty(info.id); - expect(lvl[info.id]).toBe(info.football); - - if (lvl[info.id].aggs) { - return recurse(lvl[info.id].aggs); - } - })(ac.toDsl()); - - expect(aggInfos).toHaveLength(1); - }); - - it("skips aggs that don't have a dsl representation", () => { - const configStates = [ - { - enabled: true, - type: 'date_histogram', - params: { field: '@timestamp', interval: '10s' }, - schema: 'segment', - }, - { - enabled: true, - type: 'count', - params: {}, - schema: 'metric', - }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const dsl = ac.toDsl(); - const histo = ac.byName('date_histogram')[0]; - const count = ac.byName('count')[0]; - - expect(dsl).toHaveProperty(histo.id); - expect(typeof dsl[histo.id]).toBe('object'); - expect(dsl[histo.id]).not.toHaveProperty('aggs'); - expect(dsl).not.toHaveProperty(count.id); - }); - - it('writes multiple metric aggregations at the same level', () => { - const configStates = [ - { - enabled: true, - type: 'date_histogram', - schema: 'segment', - params: { field: '@timestamp', interval: '10s' }, - }, - { enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { enabled: true, type: 'sum', schema: 'metric', params: { field: 'bytes' } }, - { enabled: true, type: 'min', schema: 'metric', params: { field: 'bytes' } }, - { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { - typesRegistry, - schemas: schemas.all, - }); - const dsl = ac.toDsl(); - const histo = ac.byName('date_histogram')[0]; - const metrics = ac.bySchemaGroup('metrics'); - - expect(dsl).toHaveProperty(histo.id); - expect(typeof dsl[histo.id]).toBe('object'); - expect(dsl[histo.id]).toHaveProperty('aggs'); - - metrics.forEach(metric => { - expect(dsl[histo.id].aggs).toHaveProperty(metric.id); - expect(dsl[histo.id].aggs[metric.id]).not.toHaveProperty('aggs'); - }); - }); - - it('writes multiple metric aggregations at every level if the vis is hierarchical', () => { - const configStates = [ - { enabled: true, type: 'terms', schema: 'segment', params: { field: 'bytes', orderBy: 1 } }, - { enabled: true, type: 'terms', schema: 'segment', params: { field: 'bytes', orderBy: 1 } }, - { enabled: true, id: '1', type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { enabled: true, type: 'sum', schema: 'metric', params: { field: 'bytes' } }, - { enabled: true, type: 'min', schema: 'metric', params: { field: 'bytes' } }, - { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const topLevelDsl = ac.toDsl(true); - const buckets = ac.bySchemaGroup('buckets'); - const metrics = ac.bySchemaGroup('metrics'); - - (function checkLevel(dsl) { - const bucket = buckets.shift(); - if (!bucket) return; - - expect(dsl).toHaveProperty(bucket.id); - - expect(typeof dsl[bucket.id]).toBe('object'); - expect(dsl[bucket.id]).toHaveProperty('aggs'); - - metrics.forEach((metric: AggConfig) => { - expect(dsl[bucket.id].aggs).toHaveProperty(metric.id); - expect(dsl[bucket.id].aggs[metric.id]).not.toHaveProperty('aggs'); - }); - - if (buckets.length) { - checkLevel(dsl[bucket.id].aggs); - } - })(topLevelDsl); - }); - - it('adds the parent aggs of nested metrics at every level if the vis is hierarchical', () => { - const configStates = [ - { - enabled: true, - id: '1', - type: 'avg_bucket', - schema: 'metric', - params: { - customBucket: { - id: '1-bucket', - type: 'date_histogram', - schema: 'bucketAgg', - params: { - field: '@timestamp', - interval: '10s', - }, - }, - customMetric: { - id: '1-metric', - type: 'count', - schema: 'metricAgg', - params: {}, - }, - }, - }, - { - enabled: true, - id: '2', - type: 'terms', - schema: 'bucket', - params: { - field: 'clientip', - }, - }, - { - enabled: true, - id: '3', - type: 'terms', - schema: 'bucket', - params: { - field: 'machine.os.raw', - }, - }, - ]; - - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); - const topLevelDsl = ac.toDsl(true)['2']; - - expect(Object.keys(topLevelDsl.aggs)).toContain('1'); - expect(Object.keys(topLevelDsl.aggs)).toContain('1-bucket'); - expect(topLevelDsl.aggs['1'].avg_bucket).toHaveProperty('buckets_path', '1-bucket>_count'); - expect(Object.keys(topLevelDsl.aggs['3'].aggs)).toContain('1'); - expect(Object.keys(topLevelDsl.aggs['3'].aggs)).toContain('1-bucket'); - expect(topLevelDsl.aggs['3'].aggs['1'].avg_bucket).toHaveProperty( - 'buckets_path', - '1-bucket>_count' - ); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts index ab70e66b1e138c..8e091ed5f21ae0 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts @@ -17,12 +17,17 @@ * under the License. */ -import _ from 'lodash'; -import { Assign } from '@kbn/utility-types'; +/** + * @name AggConfig + * + * @extends IndexedArray + * + * @description A "data structure"-like class with methods for indexing and + * accessing instances of AggConfig. + */ +import _ from 'lodash'; import { AggConfig, AggConfigOptions, IAggConfig } from './agg_config'; -import { IAggType } from './agg_type'; -import { AggTypesRegistryStart } from './agg_types_registry'; import { Schema } from './schemas'; import { AggGroupNames } from './agg_groups'; import { @@ -50,24 +55,6 @@ function parseParentAggs(dslLvlCursor: any, dsl: any) { } } -export interface AggConfigsOptions { - schemas?: Schemas; - typesRegistry: AggTypesRegistryStart; -} - -export type CreateAggConfigParams = Assign; - -/** - * @name AggConfigs - * - * @description A "data structure"-like class with methods for indexing and - * accessing instances of AggConfig. This should never be instantiated directly - * outside of this plugin. Rather, downstream plugins should do this via - * `createAggConfigs()` - * - * @internal - */ - // TODO need to make a more explicit interface for this export type IAggConfigs = AggConfigs; @@ -75,31 +62,23 @@ export class AggConfigs { public indexPattern: IndexPattern; public schemas: any; public timeRange?: TimeRange; - private readonly typesRegistry: AggTypesRegistryStart; aggs: IAggConfig[]; - constructor( - indexPattern: IndexPattern, - configStates: CreateAggConfigParams[] = [], - opts: AggConfigsOptions - ) { - this.typesRegistry = opts.typesRegistry; - + constructor(indexPattern: IndexPattern, configStates = [] as any, schemas?: any) { configStates = AggConfig.ensureIds(configStates); this.aggs = []; this.indexPattern = indexPattern; - this.schemas = opts.schemas; + this.schemas = schemas; configStates.forEach((params: any) => this.createAggConfig(params)); - if (this.schemas) { - this.initializeDefaultsFromSchemas(this.schemas); + if (schemas) { + this.initializeDefaultsFromSchemas(schemas); } } - // do this wherever the schemas were passed in, & pass in state defaults instead initializeDefaultsFromSchemas(schemas: Schemas) { // Set the defaults for any schema which has them. If the defaults // for some reason has more then the max only set the max number @@ -112,11 +91,10 @@ export class AggConfigs { }) .each((schema: any) => { if (!this.aggs.find((agg: AggConfig) => agg.schema && agg.schema.name === schema.name)) { - // the result here should be passable as a configState const defaults = schema.defaults.slice(0, schema.max); _.each(defaults, defaultState => { const state = _.defaults({ id: AggConfig.nextId(this.aggs) }, defaultState); - this.createAggConfig(state as AggConfigOptions); + this.aggs.push(new AggConfig(this, state as AggConfigOptions)); }); } }) @@ -146,36 +124,28 @@ export class AggConfigs { if (!enabledOnly) return true; return agg.enabled; }; - - const aggConfigs = new AggConfigs(this.indexPattern, this.aggs.filter(filterAggs), { - schemas: this.schemas, - typesRegistry: this.typesRegistry, - }); - + const aggConfigs = new AggConfigs( + this.indexPattern, + this.aggs.filter(filterAggs), + this.schemas + ); return aggConfigs; } createAggConfig = ( - params: CreateAggConfigParams, + params: AggConfig | AggConfigOptions, { addToAggConfigs = true } = {} ) => { - const { type } = params; let aggConfig; - if (params instanceof AggConfig) { aggConfig = params; params.parent = this; } else { - aggConfig = new AggConfig(this, { - ...params, - type: typeof type === 'string' ? this.typesRegistry.get(type) : type, - }); + aggConfig = new AggConfig(this, params); } - if (addToAggConfigs) { this.aggs.push(aggConfig); } - return aggConfig as T; }; @@ -196,10 +166,10 @@ export class AggConfigs { return true; } - toDsl(hierarchical: boolean = false): Record { + toDsl(hierarchical: boolean = false) { const dslTopLvl = {}; let dslLvlCursor: Record; - let nestedMetrics: Array<{ config: AggConfig; dsl: Record }> | []; + let nestedMetrics: Array<{ config: AggConfig; dsl: any }> | []; if (hierarchical) { // collect all metrics, and filter out the ones that we won't be copying diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts index b08fcf309e9ed6..30ab272537dad1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts @@ -23,6 +23,8 @@ import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; import { AggParamType } from '../aggs/param_types/agg'; +jest.mock('ui/new_platform'); + describe('AggParams class', () => { describe('constructor args', () => { it('accepts an array of param defs', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts index c78e56dd25887d..6d4c2d1317f505 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts @@ -19,16 +19,11 @@ import { AggType, AggTypeConfig } from './agg_type'; import { IAggConfig } from './agg_config'; -import { mockDataServices } from './test_helpers'; -import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setFieldFormats } from '../../../../../../plugins/data/public/services'; +import { npStart } from 'ui/new_platform'; -describe('AggType Class', () => { - beforeEach(() => { - mockDataServices(); - }); +jest.mock('ui/new_platform'); +describe('AggType Class', () => { describe('constructor', () => { it("requires a valid config object as it's first param", () => { expect(() => { @@ -158,10 +153,7 @@ describe('AggType Class', () => { }); it('returns default formatter', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn(() => 'default') as any, - }); + npStart.plugins.data.fieldFormats.getDefaultInstance = jest.fn(() => 'default') as any; const aggType = new AggType({ name: 'name', diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts index 3cd9496d3f23d0..5ccf0f65c0e921 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts @@ -19,6 +19,7 @@ import { constant, noop, identity } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { initParams } from './agg_params'; import { AggConfig } from './agg_config'; @@ -31,8 +32,6 @@ import { IFieldFormat, ISearchSource, } from '../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../plugins/data/public/services'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -66,7 +65,7 @@ export interface AggTypeConfig< const getFormat = (agg: AggConfig) => { const field = agg.getField(); - const fieldFormatsService = getFieldFormats(); + const fieldFormatsService = npStart.plugins.data.fieldFormats; return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts deleted file mode 100644 index 405f83e237de83..00000000000000 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { - AggTypesRegistry, - AggTypesRegistrySetup, - AggTypesRegistryStart, -} from './agg_types_registry'; -import { BucketAggType } from './buckets/_bucket_agg_type'; -import { MetricAggType } from './metrics/metric_agg_type'; - -const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType; -const metricType = { name: 'count', type: 'metric' } as MetricAggType; - -describe('AggTypesRegistry', () => { - let registry: AggTypesRegistry; - let setup: AggTypesRegistrySetup; - let start: AggTypesRegistryStart; - - beforeEach(() => { - registry = new AggTypesRegistry(); - setup = registry.setup(); - start = registry.start(); - }); - - it('registerBucket adds new buckets', () => { - setup.registerBucket(bucketType); - expect(start.getBuckets()).toEqual([bucketType]); - }); - - it('registerBucket throws error when registering duplicate bucket', () => { - expect(() => { - setup.registerBucket(bucketType); - setup.registerBucket(bucketType); - }).toThrow(/already been registered with name: terms/); - }); - - it('registerMetric adds new metrics', () => { - setup.registerMetric(metricType); - expect(start.getMetrics()).toEqual([metricType]); - }); - - it('registerMetric throws error when registering duplicate metric', () => { - expect(() => { - setup.registerMetric(metricType); - setup.registerMetric(metricType); - }).toThrow(/already been registered with name: count/); - }); - - it('gets either buckets or metrics by id', () => { - setup.registerBucket(bucketType); - setup.registerMetric(metricType); - expect(start.get('terms')).toEqual(bucketType); - expect(start.get('count')).toEqual(metricType); - }); - - it('getBuckets retrieves only buckets', () => { - setup.registerBucket(bucketType); - expect(start.getBuckets()).toEqual([bucketType]); - }); - - it('getMetrics retrieves only metrics', () => { - setup.registerMetric(metricType); - expect(start.getMetrics()).toEqual([metricType]); - }); - - it('getAll returns all buckets and metrics', () => { - setup.registerBucket(bucketType); - setup.registerMetric(metricType); - expect(start.getAll()).toEqual({ - buckets: [bucketType], - metrics: [metricType], - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts deleted file mode 100644 index 8a8746106ae587..00000000000000 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { BucketAggType } from './buckets/_bucket_agg_type'; -import { MetricAggType } from './metrics/metric_agg_type'; - -export type AggTypesRegistrySetup = ReturnType; -export type AggTypesRegistryStart = ReturnType; - -export class AggTypesRegistry { - private readonly bucketAggs = new Map(); - private readonly metricAggs = new Map(); - - setup = () => { - return { - registerBucket: >(type: T): void => { - const { name } = type; - if (this.bucketAggs.get(name)) { - throw new Error(`Bucket agg has already been registered with name: ${name}`); - } - this.bucketAggs.set(name, type); - }, - registerMetric: >(type: T): void => { - const { name } = type; - if (this.metricAggs.get(name)) { - throw new Error(`Metric agg has already been registered with name: ${name}`); - } - this.metricAggs.set(name, type); - }, - }; - }; - - start = () => { - return { - get: (name: string) => { - return this.bucketAggs.get(name) || this.metricAggs.get(name); - }, - getBuckets: () => { - return Array.from(this.bucketAggs.values()); - }, - getMetrics: () => { - return Array.from(this.metricAggs.values()); - }, - getAll: () => { - return { - buckets: Array.from(this.bucketAggs.values()), - metrics: Array.from(this.metricAggs.values()), - }; - }, - }; - }; -} diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts index d6ab58d5250a8c..546d054c5af978 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts @@ -17,16 +17,16 @@ * under the License. */ -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; -export interface IBucketAggConfig extends IAggConfig { +export interface IBucketAggConfig extends AggConfig { type: InstanceType; } -export interface BucketAggParam +export interface BucketAggParam extends AggParamType { scriptable?: boolean; filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; @@ -34,12 +34,12 @@ export interface BucketAggParam const bucketType = 'buckets'; -interface BucketAggTypeConfig +interface BucketAggTypeConfig extends AggTypeConfig> { - getKey?: (bucket: any, key: any, agg: IAggConfig) => any; + getKey?: (bucket: any, key: any, agg: AggConfig) => any; } -export class BucketAggType extends AggType< +export class BucketAggType extends AggType< TBucketAggConfig, BucketAggParam > { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts index 393d3b745250f4..e196687607d198 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { i18n } from '@kbn/i18n'; import { IBucketAggConfig } from './_bucket_agg_type'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 2b47dc384bca2f..0d3f58c50a42e7 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -21,22 +21,14 @@ import moment from 'moment'; import { createFilterDateHistogram } from './date_histogram'; import { intervalOptions } from '../_interval_options'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; -import { dateHistogramBucketAgg, IBucketDateHistogramAggConfig } from '../date_histogram'; +import { IBucketDateHistogramAggConfig } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../../../../plugins/data/public'; -// TODO: remove this once time buckets is migrated jest.mock('ui/new_platform'); describe('AggConfig Filters', () => { describe('date_histogram', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry([dateHistogramBucketAgg]); - let agg: IBucketDateHistogramAggConfig; let filter: RangeFilter; let bucketStart: any; @@ -64,7 +56,7 @@ describe('AggConfig Filters', () => { params: { field: field.name, interval, customInterval: '5d' }, }, ], - { typesRegistry } + null ); const bucketKey = 1422579600000; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index c594c7718e58bb..41e806668337e2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -18,17 +18,16 @@ */ import moment from 'moment'; -import { dateRangeBucketAgg } from '../date_range'; import { createFilterDateRange } from './date_range'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; -import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; +jest.mock('ui/new_platform'); + describe('AggConfig Filters', () => { describe('Date range', () => { - const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -56,7 +55,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 3b9c771e0f15f9..34cf996826865f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -16,21 +16,14 @@ * specific language governing permissions and limitations * under the License. */ - -import { filtersBucketAgg } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { IBucketAggConfig } from '../_bucket_agg_type'; +jest.mock('ui/new_platform'); + describe('AggConfig Filters', () => { describe('filters', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry([filtersBucketAgg]); - const getAggConfigs = () => { const field = { name: 'bytes', @@ -59,7 +52,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + null ); }; it('should return a filters filter', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index b046c802c58c15..9f845847df5d9d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -16,22 +16,16 @@ * specific language governing permissions and limitations * under the License. */ - import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; +jest.mock('ui/new_platform'); + describe('AggConfig Filters', () => { describe('histogram', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry(); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -61,7 +55,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index 7572c48390dc25..e92ba5cb2852a1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -17,18 +17,17 @@ * under the License. */ -import { ipRangeBucketAgg } from '../ip_range'; import { createFilterIpRange } from './ip_range'; -import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; -import { mockAggTypesRegistry } from '../../test_helpers'; +import { AggConfigs } from '../../agg_configs'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; +jest.mock('ui/new_platform'); + describe('AggConfig Filters', () => { describe('IP range', () => { - const typesRegistry = mockAggTypesRegistry([ipRangeBucketAgg]); - const getAggConfigs = (aggs: CreateAggConfigParams[]) => { + const getAggConfigs = (aggs: Array>) => { const field = { name: 'ip', format: fieldFormats.IpFormat, @@ -43,7 +42,7 @@ describe('AggConfig Filters', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, { typesRegistry }); + return new AggConfigs(indexPattern, aggs, null); }; it('should return a range filter for ip_range agg', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 324d4252908324..33344ca0a34845 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -17,22 +17,16 @@ * under the License. */ -import { rangeBucketAgg } from '../range'; import { createFilterRange } from './range'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; +jest.mock('ui/new_platform'); + describe('AggConfig Filters', () => { describe('range', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -62,7 +56,7 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 6db6eb11a5f527..7c6e769437ca1d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -17,18 +17,17 @@ * under the License. */ -import { termsBucketAgg } from '../terms'; import { createFilterTerms } from './terms'; -import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; -import { mockAggTypesRegistry } from '../../test_helpers'; +import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { Filter, ExistsFilter } from '../../../../../../../../plugins/data/public'; +jest.mock('ui/new_platform'); + describe('AggConfig Filters', () => { describe('terms', () => { - const typesRegistry = mockAggTypesRegistry([termsBucketAgg]); - const getAggConfigs = (aggs: CreateAggConfigParams[]) => { + const getAggConfigs = (aggs: Array>) => { const indexPattern = { id: '1234', title: 'logstash-*', @@ -43,7 +42,7 @@ describe('AggConfig Filters', () => { indexPattern, }; - return new AggConfigs(indexPattern, aggs, { typesRegistry }); + return new AggConfigs(indexPattern, aggs, null); }; it('should return a match_phrase filter for terms', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts index a5368135728d49..dc0f9baa6d0cc7 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -21,7 +21,8 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -// TODO need to move TimeBuckets +import { npStart } from 'ui/new_platform'; +import { timefilter } from 'ui/timefilter'; import { TimeBuckets } from 'ui/time_buckets'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -32,8 +33,6 @@ import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getQueryService, getUiSettings } from '../../../../../../../plugins/data/public/services'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -41,7 +40,6 @@ const tzOffset = moment().format('Z'); const getInterval = (agg: IBucketAggConfig): string => _.get(agg, ['params', 'interval']); export const setBounds = (agg: IBucketDateHistogramAggConfig, force?: boolean) => { - const { timefilter } = getQueryService().timefilter; if (agg.buckets._alreadySet && !force) return; agg.buckets._alreadySet = true; const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; @@ -223,7 +221,7 @@ export const dateHistogramBucketAgg = new BucketAggType { - beforeEach(() => { - mockDataServices(); - }); +import { npStart } from 'ui/new_platform'; - const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); +jest.mock('ui/new_platform'); +describe('date_range params', () => { const getAggConfigs = (params: Record = {}, hasIncludeTypeMeta: boolean = true) => { const field = { name: 'bytes', @@ -67,7 +58,7 @@ describe('date_range params', () => { params, }, ], - { typesRegistry } + null ); }; @@ -104,11 +95,7 @@ describe('date_range params', () => { }); it('should use the Kibana time_zone if no parameter specified', () => { - const core = coreMock.createStart(); - setUiSettings({ - ...core.uiSettings, - get: () => 'kibanaTimeZone' as any, - }); + npStart.core.uiSettings.get = jest.fn(() => 'kibanaTimeZone' as any); const aggConfigs = getAggConfigs( { @@ -119,8 +106,6 @@ describe('date_range params', () => { const dateRange = aggConfigs.aggs[0]; const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; - setUiSettings(core.uiSettings); // clean up - expect(params.time_zone).toBe('kibanaTimeZone'); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts index 933cdd0577f8da..1dc24ca80035c0 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts @@ -16,20 +16,18 @@ * specific language governing permissions and limitations * under the License. */ - import { get } from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; +import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats, getUiSettings } from '../../../../../../../plugins/data/public/services'; -import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; -export { convertDateRangeToString, DateRangeKey }; // for BWC +export { convertDateRangeToString, DateRangeKey }; const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -43,7 +41,7 @@ export const dateRangeBucketAgg = new BucketAggType({ return { from, to }; }, getFormat(agg) { - const fieldFormatsService = getFieldFormats(); + const fieldFormatsService = npStart.plugins.data.fieldFormats; const formatter = agg.fieldOwnFormatter( fieldFormats.TEXT_CONTEXT_TYPE, @@ -94,7 +92,7 @@ export const dateRangeBucketAgg = new BucketAggType({ ]); } if (!tz) { - const config = getUiSettings(); + const config = npStart.core.uiSettings; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); const isDefaultTimezone = config.isDefault('dateFormat:tz'); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts index 80efc0cf92071d..b52e2d6cfd4df4 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { i18n } from '@kbn/i18n'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts index 2852f3e4bdf464..6eaf788b83c04a 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts @@ -18,21 +18,19 @@ */ import _ from 'lodash'; +import angular from 'angular'; + import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; - import { createFilterFilters } from './create_filter/filters'; -import { toAngularJSON } from '../utils'; import { BucketAggType } from './_bucket_agg_type'; -import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../../plugins/kibana_utils/public'; - import { getQueryLog, esQuery, Query } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getUiSettings } from '../../../../../../../plugins/data/public/services'; +import { BUCKET_TYPES } from './bucket_agg_types'; const config = chrome.getUiSettingsClient(); +const storage = new Storage(window.localStorage); const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -54,17 +52,15 @@ export const filtersBucketAgg = new BucketAggType({ params: [ { name: 'filters', - // TODO need to get rid of reference to `config` below default: [{ input: { query: '', language: config.get('search:queryLanguage') }, label: '' }], write(aggConfig, output) { - const uiSettings = getUiSettings(); const inFilters: FilterValue[] = aggConfig.params.filters; if (!_.size(inFilters)) return; inFilters.forEach(filter => { const persistedLog = getQueryLog( - uiSettings, - new Storage(window.localStorage), + config, + storage, 'vis_default_editor', filter.input.language ); @@ -81,13 +77,7 @@ export const filtersBucketAgg = new BucketAggType({ return; } - const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); - const query = esQuery.buildEsQuery( - aggConfig.getIndexPattern(), - [input], - [], - esQueryConfigs - ); + const query = esQuery.buildEsQuery(aggConfig.getIndexPattern(), [input], [], config); if (!query) { console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console @@ -100,7 +90,7 @@ export const filtersBucketAgg = new BucketAggType({ matchAllLabel || (typeof filter.input.query === 'string' ? filter.input.query - : toAngularJSON(filter.input.query)); + : angular.toJson(filter.input.query)); filters[label] = { query }; }, {} diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 09dd03c759155e..f0ad5954764867 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -19,13 +19,12 @@ import { geoHashBucketAgg } from './geo_hash'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './_bucket_agg_type'; +jest.mock('ui/new_platform'); + describe('Geohash Agg', () => { - // const typesRegistry = mockAggTypesRegistry([geoHashBucketAgg]); - const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params?: Record) => { const indexPattern = { id: '1234', @@ -63,7 +62,7 @@ describe('Geohash Agg', () => { }, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts index 9142a30338163c..57e8f6e8c5ded4 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; +import { AggConfigOptions } from '../agg_config'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -56,7 +57,7 @@ export const geoTileBucketAgg = new BucketAggType({ aggs.push(agg); if (useGeocentroid) { - const aggConfig = { + const aggConfig: AggConfigOptions = { type: METRIC_TYPES.GEO_CENTROID, enabled: true, params: { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts index 11dc8e42fd6538..4e89d7db1ff647 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,23 +17,16 @@ * under the License. */ -import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { npStart } from 'ui/new_platform'; +import { AggConfigs } from '../index'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram'; import { BucketAggType } from './_bucket_agg_type'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setUiSettings } from '../../../../../../../plugins/data/public/services'; -describe('Histogram Agg', () => { - beforeEach(() => { - mockDataServices(); - }); +jest.mock('ui/new_platform'); - const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]); - - const getAggConfigs = (params: Record) => { +describe('Histogram Agg', () => { + const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', title: 'logstash-*', @@ -52,13 +45,16 @@ describe('Histogram Agg', () => { indexPattern, [ { + field: { + name: 'field', + }, id: 'test', type: BUCKET_TYPES.HISTOGRAM, schema: 'segment', params, }, ], - { typesRegistry } + null ); }; @@ -162,15 +158,10 @@ describe('Histogram Agg', () => { aggConfig.setAutoBounds(autoBounds); } - const core = coreMock.createStart(); - setUiSettings({ - ...core.uiSettings, - get: () => maxBars as any, - }); + // mock histogram:maxBars value; + npStart.core.uiSettings.get = jest.fn(() => maxBars as any); - const interval = aggConfig.write(aggConfigs).params; - setUiSettings(core.uiSettings); // clean up - return interval; + return aggConfig.write(aggConfigs).params; }; it('will respect the histogram:maxBars setting', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts index 70df2f230db094..f7e9ef45961e04 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts @@ -19,13 +19,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; +import { npStart } from 'ui/new_platform'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; -import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getNotifications, getUiSettings } from '../../../../../../../plugins/data/public/services'; +import { BUCKET_TYPES } from './bucket_agg_types'; export interface AutoBounds { min: number; @@ -37,6 +37,8 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { getAutoBounds: () => AutoBounds; } +const getUIConfig = () => npStart.core.uiSettings; + export const histogramBucketAgg = new BucketAggType({ name: BUCKET_TYPES.HISTOGRAM, title: i18n.translate('data.search.aggs.buckets.histogramTitle', { @@ -114,7 +116,7 @@ export const histogramBucketAgg = new BucketAggType({ }) .catch((e: Error) => { if (e.name === 'AbortError') return; - getNotifications().toasts.addWarning( + toastNotifications.addWarning( i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { defaultMessage: 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', @@ -134,7 +136,7 @@ export const histogramBucketAgg = new BucketAggType({ const range = autoBounds.max - autoBounds.min; const bars = range / interval; - const config = getUiSettings(); + const config = getUIConfig(); if (bars > config.get('histogram:maxBars')) { const minInterval = range / config.get('histogram:maxBars'); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts index 3fb464d8fa7a8b..91bdf53e7f8091 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts @@ -19,17 +19,15 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; +import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; +// @ts-ignore import { createFilterIpRange } from './create_filter/ip_range'; import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; - -import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; -export { IpRangeKey, convertIPRangeToString }; // for BWC - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; +export { IpRangeKey, convertIPRangeToString }; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', @@ -46,7 +44,7 @@ export const ipRangeBucketAgg = new BucketAggType({ return { type: 'range', from: bucket.from, to: bucket.to }; }, getFormat(agg) { - const fieldFormatsService = getFieldFormats(); + const fieldFormatsService = npStart.plugins.data.fieldFormats; const formatter = agg.fieldOwnFormatter( fieldFormats.TEXT_CONTEXT_TYPE, fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.IP) diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index d94477b588f8d1..77e84e044de55a 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -19,10 +19,10 @@ import { isString, isObject } from 'lodash'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; export const isType = (type: string) => { - return (agg: IAggConfig): boolean => { + return (agg: AggConfig): boolean => { const field = agg.params.field; return field && field.type === type; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts index 096b19fe7de66d..b1b0c4bc30a58e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts @@ -17,12 +17,12 @@ * under the License. */ -import { rangeBucketAgg } from './range'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, fieldFormats } from '../../../../../../../plugins/data/public'; +jest.mock('ui/new_platform'); + const buckets = [ { to: 1024, @@ -44,12 +44,6 @@ const buckets = [ ]; describe('Range Agg', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -86,7 +80,7 @@ describe('Range Agg', () => { }, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts index cee3ed506c29c4..37b829bfc20fb2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -17,16 +17,17 @@ * under the License. */ -import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; +import { AggConfigs } from '../index'; +import { IAggConfigs } from '../types'; import { BUCKET_TYPES } from './bucket_agg_types'; import { significantTermsBucketAgg } from './significant_terms'; import { IBucketAggConfig } from './_bucket_agg_type'; +jest.mock('ui/new_platform'); + describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { - const typesRegistry = mockAggTypesRegistry([significantTermsBucketAgg]); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -52,7 +53,7 @@ describe('Significant Terms Agg', () => { params, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts index 9a4f28afd3edf2..24ac332ae4d55c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts @@ -17,13 +17,13 @@ * under the License. */ -import { AggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; +import { AggConfigs } from '../index'; import { BUCKET_TYPES } from './bucket_agg_types'; +jest.mock('ui/new_platform'); + describe('Terms Agg', () => { describe('order agg editor UI', () => { - const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -48,7 +48,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts index 0de1c31d02f961..cc1288d339692f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts @@ -19,12 +19,13 @@ import { IndexPattern } from '../../../../../../../plugins/data/public'; import { AggTypeFilters } from './agg_type_filters'; -import { IAggConfig, IAggType } from '../types'; +import { AggConfig } from '..'; +import { IAggType } from '../types'; describe('AggTypeFilters', () => { let registry: AggTypeFilters; const indexPattern = ({ id: '1234', fields: [], title: 'foo' } as unknown) as IndexPattern; - const aggConfig = {} as IAggConfig; + const aggConfig = {} as AggConfig; beforeEach(() => { registry = new AggTypeFilters(); diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts index 13a4cc0856b090..d3b38ce041d7ee 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { IndexPattern } from 'src/plugins/data/public'; import { IAggConfig, IAggType } from '../types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts index 32cda7b950e93d..431e1161e0dbdf 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import expect from '@kbn/expect'; import { propFilter } from './prop_filter'; describe('prop filter', () => { @@ -46,48 +47,48 @@ describe('prop filter', () => { it('returns list when no filters are provided', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects)).toEqual(objects); + expect(nameFilter(objects)).to.eql(objects); }); it('returns list when empty list of filters is provided', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects, [])).toEqual(objects); + expect(nameFilter(objects, [])).to.eql(objects); }); it('should keep only the tables', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects, 'table')).toEqual(getObjects('table', 'table')); + expect(nameFilter(objects, 'table')).to.eql(getObjects('table', 'table')); }); it('should support comma-separated values', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, 'table,line')).toEqual(getObjects('table', 'line')); + expect(nameFilter(objects, 'table,line')).to.eql(getObjects('table', 'line')); }); it('should support an array of values', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, ['table', 'line'])).toEqual(getObjects('table', 'line')); + expect(nameFilter(objects, ['table', 'line'])).to.eql(getObjects('table', 'line')); }); it('should return all objects', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, '*')).toEqual(objects); + expect(nameFilter(objects, '*')).to.eql(objects); }); it('should allow negation', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, ['!line'])).toEqual(getObjects('table', 'pie')); + expect(nameFilter(objects, ['!line'])).to.eql(getObjects('table', 'pie')); }); it('should support a function for specifying what should be kept', () => { const objects = getObjects('table', 'line', 'pie'); const line = (value: string) => value === 'line'; - expect(nameFilter(objects, line)).toEqual(getObjects('line')); + expect(nameFilter(objects, line)).to.eql(getObjects('line')); }); it('gracefully handles a filter function with zero arity', () => { const objects = getObjects('table', 'line', 'pie'); const rejectEverything = () => false; - expect(nameFilter(objects, rejectEverything)).toEqual([]); + expect(nameFilter(objects, rejectEverything)).to.eql([]); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.test.ts b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts index 4d0cd55b09d533..a867769a77fc1d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/index.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts @@ -25,6 +25,8 @@ import { isMetricAggType } from './metrics/metric_agg_type'; const bucketAggs = aggTypes.buckets; const metricAggs = aggTypes.metrics; +jest.mock('ui/new_platform'); + describe('AggTypesComponent', () => { describe('bucket aggs', () => { it('all extend BucketAggType', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.ts b/src/legacy/core_plugins/data/public/search/aggs/index.ts index f6914c36f6c05c..0bdb92b8de65e8 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/index.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/index.ts @@ -17,13 +17,8 @@ * under the License. */ -export { - AggTypesRegistry, - AggTypesRegistrySetup, - AggTypesRegistryStart, -} from './agg_types_registry'; -export { AggType } from './agg_type'; export { aggTypes } from './agg_types'; +export { AggType } from './agg_type'; export { AggConfig } from './agg_config'; export { AggConfigs } from './agg_configs'; export { FieldParamType } from './param_types'; @@ -57,4 +52,4 @@ export { METRIC_TYPES } from './metrics/metric_agg_types'; export { ISchemas, Schema, Schemas } from './schemas'; // types -export { CreateAggConfigParams, IAggConfig, IAggConfigs } from './types'; +export { IAggConfig, IAggConfigs } from './types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts index 11bb5592747297..9fb28f8631bc63 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; + import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts index 0668a9bcf57a8f..83837f0de51146 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -18,6 +18,7 @@ */ import { i18n } from '@kbn/i18n'; + import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts index 8f728cb5e7e426..d96197693dc2ee 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts index 4f7b6e555ca33e..147e9255210887 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts @@ -18,11 +18,10 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', @@ -38,7 +37,7 @@ export const cardinalityMetricAgg = new MetricAggType({ }); }, getFormat() { - const fieldFormatsService = getFieldFormats(); + const fieldFormatsService = npStart.plugins.data.fieldFormats; return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts index 8b3e0a488c68a1..14a9bd073ff2bc 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts @@ -18,11 +18,10 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; export const countMetricAgg = new MetricAggType({ name: METRIC_TYPES.COUNT, @@ -36,7 +35,7 @@ export const countMetricAgg = new MetricAggType({ }); }, getFormat() { - const fieldFormatsService = getFieldFormats(); + const fieldFormatsService = npStart.plugins.data.fieldFormats; return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts index 00d866e6f2b3ed..054543de3dd067 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { assign } from 'lodash'; import { IMetricAggConfig } from '../metric_agg_type'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts index 88549ee3019ee6..e24aca08271c72 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts @@ -23,6 +23,7 @@ import { noop, identity } from 'lodash'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { parentPipelineAggWriter } from './parent_pipeline_agg_writer'; + import { Schemas } from '../../schemas'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index 05e009cc9da30e..e7c98e575fdb4f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -21,6 +21,7 @@ import { identity } from 'lodash'; import { i18n } from '@kbn/i18n'; import { siblingPipelineAggWriter } from './sibling_pipeline_agg_writer'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; + import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { Schemas } from '../../schemas'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts index ad55837ec9a300..4755a873e69779 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,16 +17,15 @@ * under the License. */ -import { medianMetricAgg } from './median'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +jest.mock('ui/new_platform'); + describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([medianMetricAgg]); const field = { name: 'bytes', }; @@ -51,7 +50,7 @@ describe('AggTypeMetricMedianProvider class', () => { }, }, ], - { typesRegistry } + null ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts index 68fc98261118c2..53a5ffff418f10 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts @@ -16,10 +16,12 @@ * specific language governing permissions and limitations * under the License. */ - import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; + +// @ts-ignore +import { percentilesMetricAgg } from './percentiles'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 952dcc96de8330..3bae7b92618dcd 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -18,14 +18,13 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; -import { FilterFieldTypes } from '../param_types/field'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; +import { FilterFieldTypes } from '../param_types/field'; export interface IMetricAggConfig extends AggConfig { type: InstanceType; @@ -79,7 +78,7 @@ export class MetricAggType { - const fieldFormatsService = getFieldFormats(); + const fieldFormatsService = npStart.plugins.data.fieldFormats; const field = agg.getField(); return field ? field.format diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts index 1806c6d9d77107..48851051634356 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 58b4ee530a8c2d..11fc39c20bdc42 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -17,12 +17,12 @@ * under the License. */ +import sinon from 'sinon'; import { derivativeMetricAgg } from './derivative'; import { cumulativeSumMetricAgg } from './cumulative_sum'; import { movingAvgMetricAgg } from './moving_avg'; import { serialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; jest.mock('../schemas', () => { @@ -34,13 +34,9 @@ jest.mock('../schemas', () => { }; }); -describe('parent pipeline aggs', function() { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry(); +jest.mock('ui/new_platform'); +describe('parent pipeline aggs', function() { const metrics = [ { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, @@ -98,7 +94,7 @@ describe('parent pipeline aggs', function() { schema: 'metric', }, ], - { typesRegistry } + null ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -224,16 +220,16 @@ describe('parent pipeline aggs', function() { }); const searchSource: any = {}; - const customMetricSpy = jest.fn(); + const customMetricSpy = sinon.spy(); const customMetric = aggConfig.params.customMetric; // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); }); - expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); + expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 628f1cd204ee5e..655e918ce07deb 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -19,16 +19,14 @@ import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +jest.mock('ui/new_platform'); + describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; beforeEach(() => { - mockDataServices(); - - const typesRegistry = mockAggTypesRegistry([percentileRanksMetricAgg]); const field = { name: 'bytes', }; @@ -60,7 +58,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }, }, ], - { typesRegistry } + null ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts index 1d640a9c1fa42f..38b47a7e97d2f2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -18,17 +18,20 @@ */ import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; + import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; // required by the values editor + export type IPercentileRanksAggConfig = IResponseAggConfig; +const getFieldFormats = () => npStart.plugins.data.fieldFormats; + const valueProps = { makeLabel(this: IPercentileRanksAggConfig) { const fieldFormatsService = getFieldFormats(); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts index e077bc0f8c7737..dd1aaca973e473 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -19,14 +19,14 @@ import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +jest.mock('ui/new_platform'); + describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([percentilesMetricAgg]); const field = { name: 'bytes', }; @@ -58,7 +58,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }, }, ], - { typesRegistry } + null ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts index 49e927d07d8dd0..39dc0d0f181e92 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts @@ -18,11 +18,15 @@ */ import { i18n } from '@kbn/i18n'; + import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; + import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; + +// @ts-ignore import { ordinalSuffix } from './lib/ordinal_suffix'; export type IPercentileAggConfig = IResponseAggConfig; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index d3456bacceb6ab..d643cf0d2a4781 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import { spy } from 'sinon'; import { bucketSumMetricAgg } from './bucket_sum'; import { bucketAvgMetricAgg } from './bucket_avg'; import { bucketMinMetricAgg } from './bucket_min'; @@ -24,7 +25,6 @@ import { bucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; jest.mock('../schemas', () => { class MockedSchemas { @@ -35,13 +35,9 @@ jest.mock('../schemas', () => { }; }); -describe('sibling pipeline aggs', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry(); +jest.mock('ui/new_platform'); +describe('sibling pipeline aggs', () => { const metrics = [ { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, @@ -100,7 +96,7 @@ describe('sibling pipeline aggs', () => { }, }, ], - { typesRegistry } + null ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -166,8 +162,8 @@ describe('sibling pipeline aggs', () => { init(); const searchSource: any = {}; - const customMetricSpy = jest.fn(); - const customBucketSpy = jest.fn(); + const customMetricSpy = spy(); + const customBucketSpy = spy(); const { customMetric, customBucket } = aggConfig.params; // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter @@ -175,11 +171,11 @@ describe('sibling pipeline aggs', () => { customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); }); - expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); - expect(customBucketSpy.mock.calls[0]).toEqual([customBucket, searchSource, {}]); + expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + expect(customBucketSpy.calledWith(customBucket, searchSource)).toBe(true); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 0679831b1e6ac8..3125026a521854 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -19,11 +19,11 @@ import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; import { AggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +jest.mock('ui/new_platform'); + describe('AggTypeMetricStandardDeviationProvider class', () => { - const typesRegistry = mockAggTypesRegistry([stdDeviationMetricAgg]); const getAggConfigs = (customLabel?: string) => { const field = { name: 'memory', @@ -52,7 +52,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { }, }, ], - { typesRegistry } + null ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts index ad1f42f5c563e6..a973de4fe8659e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -20,10 +20,11 @@ import { dropRight, last } from 'lodash'; import { topHitMetricAgg } from './top_hit'; import { AggConfigs } from '../agg_configs'; -import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +jest.mock('ui/new_platform'); + describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; @@ -36,7 +37,6 @@ describe('Top hit metric', () => { fieldType = KBN_FIELD_TYPES.NUMBER, size = 1, }: any) => { - const typesRegistry = mockAggTypesRegistry([topHitMetricAgg]); const field = { name: fieldName, displayName: fieldName, @@ -81,7 +81,7 @@ describe('Top hit metric', () => { params, }, ], - { typesRegistry } + null ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts index d31abe64491d0d..2e7c11004b472e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts @@ -17,10 +17,10 @@ * under the License. */ -import { AggConfig, IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; import { BaseParamType } from './base'; -export class AggParamType extends BaseParamType< +export class AggParamType extends BaseParamType< TAggConfig > { makeAgg: (agg: TAggConfig, state?: any) => TAggConfig; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts index 95ad71a616ab27..1523cb03eb966c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts @@ -18,10 +18,10 @@ */ import { IAggConfigs } from '../agg_configs'; -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; import { FetchOptions, ISearchSource } from '../../../../../../../plugins/data/public'; -export class BaseParamType { +export class BaseParamType { name: string; type: string; displayName: string; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts index 7338c41f920d72..fa88754ac60b94 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts @@ -25,6 +25,8 @@ import { IAggConfig } from '../agg_config'; import { IMetricAggConfig } from '../metrics/metric_agg_type'; import { Schema } from '../schemas'; +jest.mock('ui/new_platform'); + describe('Field', () => { const indexPattern = { id: '1234', diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts index bb5707cbb482ec..40c30f6210a833 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { isFunction } from 'lodash'; +import { npStart } from 'ui/new_platform'; import { IAggConfig } from '../agg_config'; import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; import { BaseParamType } from './base'; @@ -29,8 +30,6 @@ import { indexPatterns, KBN_FIELD_TYPES, } from '../../../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getNotifications } from '../../../../../../../plugins/data/public/services'; const filterByType = propFilter('type'); @@ -94,7 +93,7 @@ export class FieldParamType extends BaseParamType { // @ts-ignore const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); if (!validField) { - getNotifications().toasts.addDanger( + npStart.core.notifications.toasts.addDanger( i18n.translate( 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts index 1a453a225797db..bc36bb46d3d166 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts @@ -17,26 +17,27 @@ * under the License. */ +import { IndexedArray } from 'ui/indexed_array'; import { AggTypeFieldFilters } from './field_filters'; -import { IAggConfig } from '../../agg_config'; +import { AggConfig } from '../../agg_config'; import { IndexPatternField } from '../../../../../../../../plugins/data/public'; describe('AggTypeFieldFilters', () => { let registry: AggTypeFieldFilters; - const aggConfig = {} as IAggConfig; + const aggConfig = {} as AggConfig; beforeEach(() => { registry = new AggTypeFieldFilters(); }); it('should filter nothing without registered filters', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; const filtered = registry.filter(fields, aggConfig); expect(filtered).toEqual(fields); }); it('should pass all fields to the registered filter', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; const filter = jest.fn(); registry.addFilter(filter); registry.filter(fields, aggConfig); @@ -45,7 +46,7 @@ describe('AggTypeFieldFilters', () => { }); it('should allow registered filters to filter out fields', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; let filtered = registry.filter(fields, aggConfig); expect(filtered).toEqual(fields); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts index 1cbf0c9ae36245..7d1348ab5423be 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts @@ -17,9 +17,9 @@ * under the License. */ import { IndexPatternField } from 'src/plugins/data/public'; -import { IAggConfig } from '../../agg_config'; +import { AggConfig } from '../../agg_config'; -type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: IAggConfig) => boolean; +type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: AggConfig) => boolean; /** * A registry to store {@link AggTypeFieldFilter} which are used to filter down @@ -41,11 +41,11 @@ class AggTypeFieldFilters { /** * Returns the {@link any|fields} filtered by all registered filters. * - * @param fields An array of fields that will be filtered down by this registry. + * @param fields An IndexedArray of fields that will be filtered down by this registry. * @param aggConfig The aggConfig for which the returning list will be used. * @return A filtered list of the passed fields. */ - public filter(fields: IndexPatternField[], aggConfig: IAggConfig) { + public filter(fields: IndexPatternField[], aggConfig: AggConfig) { const allFilters = Array.from(this.filters); const allowedAggTypeFields = fields.filter(field => { const isAggTypeFieldAllowed = allFilters.every(filter => filter(field, aggConfig)); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts index 12fd29b3a14527..827299814c62aa 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts @@ -19,11 +19,13 @@ import { BaseParamType } from './base'; import { JsonParamType } from './json'; -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; + +jest.mock('ui/new_platform'); describe('JSON', function() { const paramName = 'json_test'; - let aggConfig: IAggConfig; + let aggConfig: AggConfig; let output: Record; const initAggParam = (config: Record = {}) => @@ -34,7 +36,7 @@ describe('JSON', function() { }); beforeEach(function() { - aggConfig = { params: {} } as IAggConfig; + aggConfig = { params: {} } as AggConfig; output = { params: {} }; }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts index bf85b3b890c358..771919b0bb56b9 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; import { BaseParamType } from './base'; export class JsonParamType extends BaseParamType { @@ -29,7 +29,7 @@ export class JsonParamType extends BaseParamType { this.name = config.name || 'json'; if (!config.write) { - this.write = (aggConfig: IAggConfig, output: Record) => { + this.write = (aggConfig: AggConfig, output: Record) => { let paramJson; const param = aggConfig.params[this.name]; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts index c03d6cdfa1c700..6b58d81914097c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts @@ -20,6 +20,8 @@ import { BaseParamType } from './base'; import { OptionedParamType } from './optioned'; +jest.mock('ui/new_platform'); + describe('Optioned', () => { describe('constructor', () => { it('it is an instance of BaseParamType', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts index 9eb7ceda607117..5ffda3740af49f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts @@ -17,14 +17,14 @@ * under the License. */ -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; import { BaseParamType } from './base'; export interface OptionedValueProp { value: string; text: string; disabled?: boolean; - isCompatible: (agg: IAggConfig) => boolean; + isCompatible: (agg: AggConfig) => boolean; } export interface OptionedParamEditorProps { @@ -40,7 +40,7 @@ export class OptionedParamType extends BaseParamType { super(config); if (!config.write) { - this.write = (aggConfig: IAggConfig, output: Record) => { + this.write = (aggConfig: AggConfig, output: Record) => { output.params[this.name] = aggConfig.params[this.name].value; }; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts index 29ec9741611a37..fd5ccebde993eb 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts @@ -19,11 +19,13 @@ import { BaseParamType } from './base'; import { StringParamType } from './string'; -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; + +jest.mock('ui/new_platform'); describe('String', function() { let paramName = 'json_test'; - let aggConfig: IAggConfig; + let aggConfig: AggConfig; let output: Record; const initAggParam = (config: Record = {}) => @@ -34,7 +36,7 @@ describe('String', function() { }); beforeEach(() => { - aggConfig = { params: {} } as IAggConfig; + aggConfig = { params: {} } as AggConfig; output = { params: {} }; }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts index 750606eb8433bf..58ba99f8a6d636 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IAggConfig } from '../agg_config'; +import { AggConfig } from '../agg_config'; import { BaseParamType } from './base'; export class StringParamType extends BaseParamType { @@ -25,7 +25,7 @@ export class StringParamType extends BaseParamType { super(config); if (!config.write) { - this.write = (aggConfig: IAggConfig, output: Record) => { + this.write = (aggConfig: AggConfig, output: Record) => { if (aggConfig.params[this.name] && aggConfig.params[this.name].length) { output.params[this.name] = aggConfig.params[this.name]; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts deleted file mode 100644 index d6bb7938664932..00000000000000 --- a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; -import { aggTypes } from '../agg_types'; -import { BucketAggType } from '../buckets/_bucket_agg_type'; -import { MetricAggType } from '../metrics/metric_agg_type'; - -/** - * Testing utility which creates a new instance of AggTypesRegistry, - * registers the provided agg types, and returns AggTypesRegistry.start() - * - * This is useful if your test depends on a certain agg type to be present - * in the registry. - * - * @param [types] - Optional array of AggTypes to register. - * If no value is provided, all default types will be registered. - * - * @internal - */ -export function mockAggTypesRegistry | MetricAggType>( - types?: T[] -): AggTypesRegistryStart { - const registry = new AggTypesRegistry(); - const registrySetup = registry.setup(); - - if (types) { - types.forEach(type => { - if (type instanceof BucketAggType) { - registrySetup.registerBucket(type); - } else if (type instanceof MetricAggType) { - registrySetup.registerMetric(type); - } - }); - } else { - aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); - aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); - } - - return registry.start(); -} diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts deleted file mode 100644 index c4e78ab8f64226..00000000000000 --- a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { coreMock } from '../../../../../../../../src/core/public/mocks'; -import { dataPluginMock } from '../../../../../../../plugins/data/public/mocks'; -import { searchStartMock } from '../../mocks'; -import { setSearchServiceShim } from '../../../services'; -import { - setFieldFormats, - setIndexPatterns, - setNotifications, - setOverlays, - setQueryService, - setSearchService, - setUiSettings, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../../plugins/data/public/services'; - -/** - * Testing helper which calls all of the service setters used in the - * data plugin. Services are added using their provided mocks. - * - * @internal - */ -export function mockDataServices() { - const core = coreMock.createStart(); - const data = dataPluginMock.createStartContract(); - const searchShim = searchStartMock(); - - setSearchServiceShim(searchShim); - setFieldFormats(data.fieldFormats); - setIndexPatterns(data.indexPatterns); - setNotifications(core.notifications); - setOverlays(core.overlays); - setQueryService(data.query); - setSearchService(data.search); - setUiSettings(core.uiSettings); -} diff --git a/src/legacy/core_plugins/data/public/search/aggs/types.ts b/src/legacy/core_plugins/data/public/search/aggs/types.ts index 5d02f426b58967..2c918abf99fcaf 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/types.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/types.ts @@ -18,7 +18,7 @@ */ export { IAggConfig } from './agg_config'; -export { CreateAggConfigParams, IAggConfigs } from './agg_configs'; +export { IAggConfigs } from './agg_configs'; export { IAggType } from './agg_type'; export { AggParam, AggParamOption } from './agg_params'; export { IFieldParamType } from './param_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx index c0662c98755a3d..a3c7f24f3927d8 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx @@ -19,6 +19,8 @@ import { isValidJson } from './utils'; +jest.mock('ui/new_platform'); + const input = { valid: '{ "test": "json input" }', invalid: 'strings are not json', diff --git a/src/legacy/core_plugins/data/public/search/aggs/utils.ts b/src/legacy/core_plugins/data/public/search/aggs/utils.ts index 67ea373f438fb8..62f07ce44ab46e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/utils.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.ts @@ -26,7 +26,7 @@ import { isValidEsInterval } from '../../../common'; * @param {string} value a string that should be validated * @returns {boolean} true if value is a valid JSON or if value is an empty string, or a string with whitespaces, otherwise false */ -export function isValidJson(value: string): boolean { +function isValidJson(value: string): boolean { if (!value || value.length === 0) { return true; } @@ -49,7 +49,7 @@ export function isValidJson(value: string): boolean { } } -export function isValidInterval(value: string, baseInterval?: string) { +function isValidInterval(value: string, baseInterval?: string) { if (baseInterval) { return _parseWithBase(value, baseInterval); } else { @@ -69,37 +69,4 @@ function _parseWithBase(value: string, baseInterval: string) { } } -// An inlined version of angular.toJSON() -// source: https://github.com/angular/angular.js/blob/master/src/Angular.js#L1312 -// @internal -export function toAngularJSON(obj: any, pretty?: any): string { - if (obj === undefined) return ''; - if (typeof pretty === 'number') { - pretty = pretty ? 2 : null; - } - return JSON.stringify(obj, toJsonReplacer, pretty); -} - -function isWindow(obj: any) { - return obj && obj.window === obj; -} - -function isScope(obj: any) { - return obj && obj.$evalAsync && obj.$watch; -} - -function toJsonReplacer(key: any, value: any) { - let val = value; - - if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { - val = undefined; - } else if (isWindow(value)) { - val = '$WINDOW'; - } else if (value && window.document === value) { - val = '$DOCUMENT'; - } else if (isScope(value)) { - val = '$SCOPE'; - } - - return val; -} +export { isValidJson, isValidInterval }; diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 24dd1c4944bfba..7a5d927d0f219a 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -19,7 +19,7 @@ import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { createAggConfigs, IAggConfigs } from 'ui/agg_types'; +import { AggConfigs, IAggConfigs } from 'ui/agg_types'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { KibanaContext, @@ -258,7 +258,7 @@ export const esaggs = (): ExpressionFunctionDefinition { - const { aggs } = getSearchServiceShim(); - const aggConfigs = aggs.createAggConfigs(indexPattern); + const aggConfigs = new AggConfigs(indexPattern); const aggConfig = aggConfigs.createAggConfig({ enabled: true, type, diff --git a/src/legacy/core_plugins/data/public/search/mocks.ts b/src/legacy/core_plugins/data/public/search/mocks.ts deleted file mode 100644 index 86b6a928dc5b42..00000000000000 --- a/src/legacy/core_plugins/data/public/search/mocks.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SearchSetup, SearchStart } from './search_service'; -import { AggTypesRegistrySetup, AggTypesRegistryStart } from './aggs/agg_types_registry'; -import { AggConfigs } from './aggs/agg_configs'; -import { mockAggTypesRegistry } from './aggs/test_helpers'; - -const aggTypeBaseParamMock = () => ({ - name: 'some_param', - type: 'some_param_type', - displayName: 'some_agg_type_param', - required: false, - advanced: false, - default: {}, - write: jest.fn(), - serialize: jest.fn().mockImplementation(() => {}), - deserialize: jest.fn().mockImplementation(() => {}), - options: [], -}); - -const aggTypeConfigMock = () => ({ - name: 'some_name', - title: 'some_title', - params: [aggTypeBaseParamMock()], -}); - -export const aggTypesRegistrySetupMock = (): MockedKeys => ({ - registerBucket: jest.fn(), - registerMetric: jest.fn(), -}); - -export const aggTypesRegistryStartMock = (): MockedKeys => ({ - get: jest.fn().mockImplementation(aggTypeConfigMock), - getBuckets: jest.fn().mockImplementation(() => [aggTypeConfigMock()]), - getMetrics: jest.fn().mockImplementation(() => [aggTypeConfigMock()]), - getAll: jest.fn().mockImplementation(() => ({ - buckets: [aggTypeConfigMock()], - metrics: [aggTypeConfigMock()], - })), -}); - -export const searchSetupMock = (): MockedKeys => ({ - aggs: { - types: aggTypesRegistrySetupMock(), - }, -}); - -export const searchStartMock = (): MockedKeys => ({ - aggs: { - createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { - return new AggConfigs(indexPattern, configStates, { - schemas, - typesRegistry: mockAggTypesRegistry(), - }); - }), - types: mockAggTypesRegistry(), - __LEGACY: { - AggConfig: jest.fn() as any, - AggType: jest.fn(), - aggTypeFieldFilters: jest.fn() as any, - FieldParamType: jest.fn(), - MetricAggType: jest.fn(), - parentPipelineAggHelper: jest.fn() as any, - setBounds: jest.fn(), - siblingPipelineAggHelper: jest.fn() as any, - }, - }, -}); diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/legacy/core_plugins/data/public/search/search_service.ts index 6754c0e3551af5..45f9ff17328adb 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/src/legacy/core_plugins/data/public/search/search_service.ts @@ -18,16 +18,11 @@ */ import { CoreSetup, CoreStart } from '../../../../../core/public'; -import { IndexPattern } from '../../../../../plugins/data/public'; import { aggTypes, AggType, - AggTypesRegistry, - AggTypesRegistrySetup, - AggTypesRegistryStart, AggConfig, AggConfigs, - CreateAggConfigParams, FieldParamType, MetricAggType, aggTypeFieldFilters, @@ -37,28 +32,20 @@ import { } from './aggs'; interface AggsSetup { - types: AggTypesRegistrySetup; + types: typeof aggTypes; } -interface AggsStartLegacy { +interface AggsStart { + types: typeof aggTypes; AggConfig: typeof AggConfig; + AggConfigs: typeof AggConfigs; AggType: typeof AggType; aggTypeFieldFilters: typeof aggTypeFieldFilters; FieldParamType: typeof FieldParamType; MetricAggType: typeof MetricAggType; parentPipelineAggHelper: typeof parentPipelineAggHelper; - setBounds: typeof setBounds; siblingPipelineAggHelper: typeof siblingPipelineAggHelper; -} - -interface AggsStart { - createAggConfigs: ( - indexPattern: IndexPattern, - configStates?: CreateAggConfigParams[], - schemas?: Record - ) => InstanceType; - types: AggTypesRegistryStart; - __LEGACY: AggsStartLegacy; + setBounds: typeof setBounds; } export interface SearchSetup { @@ -76,41 +63,28 @@ export interface SearchStart { * it will move into the existing search service in src/plugins/data/public/search */ export class SearchService { - private readonly aggTypesRegistry = new AggTypesRegistry(); - public setup(core: CoreSetup): SearchSetup { - const aggTypesSetup = this.aggTypesRegistry.setup(); - aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); - aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m)); - return { aggs: { - types: aggTypesSetup, + types: aggTypes, // TODO convert to registry + // TODO add other items as needed }, }; } public start(core: CoreStart): SearchStart { - const aggTypesStart = this.aggTypesRegistry.start(); return { aggs: { - createAggConfigs: (indexPattern, configStates = [], schemas) => { - return new AggConfigs(indexPattern, configStates, { - schemas, - typesRegistry: aggTypesStart, - }); - }, - types: aggTypesStart, - __LEGACY: { - AggConfig, // TODO make static - AggType, - aggTypeFieldFilters, - FieldParamType, - MetricAggType, - parentPipelineAggHelper, // TODO make static - setBounds, // TODO make static - siblingPipelineAggHelper, // TODO make static - }, + types: aggTypes, // TODO convert to registry + AggConfig, // TODO make static + AggConfigs, + AggType, + aggTypeFieldFilters, + FieldParamType, + MetricAggType, + parentPipelineAggHelper, // TODO make static + siblingPipelineAggHelper, // TODO make static + setBounds, // TODO make static }, }; } diff --git a/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts b/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts index 98048cb25db2fe..ef2748102623ab 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts @@ -20,6 +20,8 @@ import { TabifyBuckets } from './buckets'; import { AggGroupNames } from '../aggs'; +jest.mock('ui/new_platform'); + describe('Buckets wrapper', () => { const check = (aggResp: any, count: number, keys: string[]) => { test('reads the length', () => { diff --git a/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts b/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts index 6c5dc790ef9767..cfd4cd7de640b2 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts @@ -18,17 +18,12 @@ */ import { tabifyGetColumns } from './get_columns'; -import { TabbedAggColumn } from './types'; import { AggConfigs, AggGroupNames, Schemas } from '../aggs'; -import { mockAggTypesRegistry, mockDataServices } from '../aggs/test_helpers'; - -describe('get columns', () => { - beforeEach(() => { - mockDataServices(); - }); +import { TabbedAggColumn } from './types'; - const typesRegistry = mockAggTypesRegistry(); +jest.mock('ui/new_platform'); +describe('get columns', () => { const createAggConfigs = (aggs: any[] = []) => { const field = { name: '@timestamp', @@ -43,17 +38,18 @@ describe('get columns', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, { - typesRegistry, - schemas: new Schemas([ + return new AggConfigs( + indexPattern, + aggs, + new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all, - }); + ]).all + ); }; test('should inject a count metric if no aggs exist', () => { diff --git a/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts b/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts index 94301eedac74a7..f5df0a683ca00c 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts @@ -19,19 +19,14 @@ import { TabbedAggResponseWriter } from './response_writer'; import { AggConfigs, AggGroupNames, Schemas, BUCKET_TYPES } from '../aggs'; -import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; import { TabbedResponseWriterOptions } from './types'; -describe('TabbedAggResponseWriter class', () => { - beforeEach(() => { - mockDataServices(); - }); +jest.mock('ui/new_platform'); +describe('TabbedAggResponseWriter class', () => { let responseWriter: TabbedAggResponseWriter; - const typesRegistry = mockAggTypesRegistry(); - const splitAggConfig = [ { type: BUCKET_TYPES.TERMS, @@ -71,17 +66,18 @@ describe('TabbedAggResponseWriter class', () => { } as any; return new TabbedAggResponseWriter( - new AggConfigs(indexPattern, aggs, { - typesRegistry, - schemas: new Schemas([ + new AggConfigs( + indexPattern, + aggs, + new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all, - }), + ]).all + ), { metricsAtAllLevels: false, partialRows: false, diff --git a/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts b/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts index db4ad3bdea96bd..13fe7719b0a856 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts @@ -20,12 +20,11 @@ import { IndexPattern } from '../../../../../../plugins/data/public'; import { tabifyAggResponse } from './tabify'; import { IAggConfig, IAggConfigs, AggGroupNames, Schemas, AggConfigs } from '../aggs'; -import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data'; -describe('tabifyAggResponse Integration', () => { - const typesRegistry = mockAggTypesRegistry(); +jest.mock('ui/new_platform'); +describe('tabifyAggResponse Integration', () => { const createAggConfigs = (aggs: IAggConfig[] = []) => { const field = { name: '@timestamp', @@ -40,17 +39,18 @@ describe('tabifyAggResponse Integration', () => { }, } as unknown) as IndexPattern; - return new AggConfigs(indexPattern, aggs, { - typesRegistry, - schemas: new Schemas([ + return new AggConfigs( + indexPattern, + aggs, + new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all, - }); + ]).all + ); }; const mockAggConfig = (agg: any): IAggConfig => (agg as unknown) as IAggConfig; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts index 6ae4e415f8caa8..6591aa5fb53d5f 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts @@ -20,8 +20,7 @@ import { cloneDeep } from 'lodash'; import { Vis, VisState } from 'src/legacy/core_plugins/visualizations/public'; - -import { createAggConfigs, IAggConfig, AggGroupNames } from '../../../legacy_imports'; +import { AggConfigs, IAggConfig, AggGroupNames } from '../../../legacy_imports'; import { EditorStateActionTypes } from './constants'; import { getEnabledMetricAggsCount } from '../../agg_group_helper'; import { EditorAction } from './actions'; @@ -33,8 +32,7 @@ function initEditorState(vis: Vis) { function editorStateReducer(state: VisState, action: EditorAction): VisState { switch (action.type) { case EditorStateActionTypes.ADD_NEW_AGG: { - const payloadAggConfig = action.payload as IAggConfig; - const aggConfig = state.aggs.createAggConfig(payloadAggConfig, { + const aggConfig = state.aggs.createAggConfig(action.payload as IAggConfig, { addToAggConfigs: false, }); aggConfig.brandNew = true; @@ -42,7 +40,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -65,7 +63,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -90,7 +88,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -131,7 +129,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -143,7 +141,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -165,7 +163,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } diff --git a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts index 8aed263c4e4d1d..832f73752a99b6 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts @@ -22,12 +22,12 @@ export { AggType, IAggType, IAggConfig, + AggConfigs, IAggConfigs, AggParam, AggGroupNames, aggGroupNamesMap, aggTypes, - createAggConfigs, FieldParamType, IFieldParamType, BUCKET_TYPES, diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index 736152c7014dc2..0e1e48d00a1b27 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -34,7 +34,7 @@ import { stubFields } from '../../../../plugins/data/public/stubs'; import { tableVisResponseHandler } from './table_vis_response_handler'; import { coreMock } from '../../../../core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { createAggConfigs } from 'ui/agg_types'; +import { AggConfigs } from 'ui/agg_types'; import { tabifyAggResponse, IAggConfig } from './legacy_imports'; jest.mock('ui/new_platform'); @@ -113,7 +113,7 @@ describe('Table Vis - Controller', () => { return ({ type: tableVisTypeDefinition, params: Object.assign({}, tableVisTypeDefinition.visConfig.defaults, params), - aggs: createAggConfigs( + aggs: new AggConfigs( stubIndexPattern, [ { type: 'count', schema: 'metric' }, diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index 0a3b1938436c0e..fb7a157b53a9ac 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -18,10 +18,10 @@ */ export { + AggConfigs, IAggConfig, IAggConfigs, isDateHistogramBucketAggConfig, setBounds, } from '../../data/public'; -export { createAggConfigs } from 'ui/agg_types'; export { createSavedSearchesLoader } from '../../../../plugins/discover/public'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js index 15a826cc6ddbe5..2f36322c67256a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js @@ -30,7 +30,7 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; import { PersistedState } from '../../../../../../../src/plugins/visualizations/public'; -import { createAggConfigs } from '../../legacy_imports'; +import { AggConfigs } from '../../legacy_imports'; import { updateVisualizationConfig } from './legacy/vis_update'; import { getTypes } from './services'; @@ -83,7 +83,7 @@ class VisImpl extends EventEmitter { updateVisualizationConfig(state.params, this.params); if (state.aggs || !this.aggs) { - this.aggs = createAggConfigs( + this.aggs = new AggConfigs( this.indexPattern, state.aggs ? state.aggs.aggs || state.aggs : [], this.type.schemas.all @@ -125,7 +125,7 @@ class VisImpl extends EventEmitter { copyCurrentState(includeDisabled = false) { const state = this.getCurrentState(includeDisabled); - state.aggs = createAggConfigs( + state.aggs = new AggConfigs( this.indexPattern, state.aggs.aggs || state.aggs, this.type.schemas.all diff --git a/src/legacy/ui/public/agg_types/index.ts b/src/legacy/ui/public/agg_types/index.ts index ffc300251c4bb3..ac5d0bed7ef153 100644 --- a/src/legacy/ui/public/agg_types/index.ts +++ b/src/legacy/ui/public/agg_types/index.ts @@ -27,19 +27,18 @@ import { start as dataStart } from '../../../core_plugins/data/public/legacy'; // runtime contracts -const { types } = dataStart.search.aggs; -export const aggTypes = types.getAll(); -export const { createAggConfigs } = dataStart.search.aggs; export const { + types: aggTypes, AggConfig, + AggConfigs, AggType, aggTypeFieldFilters, FieldParamType, MetricAggType, parentPipelineAggHelper, - setBounds, siblingPipelineAggHelper, -} = dataStart.search.aggs.__LEGACY; + setBounds, +} = dataStart.search.aggs; // types export { diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js new file mode 100644 index 00000000000000..9e53044f681baa --- /dev/null +++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js @@ -0,0 +1,485 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import sinon from 'sinon'; +import expect from '@kbn/expect'; +import ngMock from 'ng_mock'; +import { AggType, AggConfig } from '../../agg_types'; +import { start as visualizationsStart } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; + +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; + +describe('AggConfig', function() { + let indexPattern; + + beforeEach(ngMock.module('kibana')); + beforeEach( + ngMock.inject(function(Private) { + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + }) + ); + + describe('#toDsl', function() { + it('calls #write()', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }); + + const aggConfig = vis.aggs.byName('date_histogram')[0]; + const stub = sinon.stub(aggConfig, 'write').returns({ params: {} }); + + aggConfig.toDsl(); + expect(stub.callCount).to.be(1); + }); + + it('uses the type name as the agg name', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }); + + const aggConfig = vis.aggs.byName('date_histogram')[0]; + sinon.stub(aggConfig, 'write').returns({ params: {} }); + + const dsl = aggConfig.toDsl(); + expect(dsl).to.have.property('date_histogram'); + }); + + it('uses the params from #write() output as the agg params', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }); + + const aggConfig = vis.aggs.byName('date_histogram')[0]; + const football = {}; + + sinon.stub(aggConfig, 'write').returns({ params: football }); + + const dsl = aggConfig.toDsl(); + expect(dsl.date_histogram).to.be(football); + }); + + it('includes subAggs from #write() output', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'avg', + schema: 'metric', + }, + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }); + + const histoConfig = vis.aggs.byName('date_histogram')[0]; + const avgConfig = vis.aggs.byName('avg')[0]; + const football = {}; + + sinon.stub(histoConfig, 'write').returns({ params: {}, subAggs: [avgConfig] }); + sinon.stub(avgConfig, 'write').returns({ params: football }); + + const dsl = histoConfig.toDsl(); + + // didn't use .eql() because of variable key names, and final check is strict + expect(dsl).to.have.property('aggs'); + expect(dsl.aggs).to.have.property(avgConfig.id); + expect(dsl.aggs[avgConfig.id]).to.have.property('avg'); + expect(dsl.aggs[avgConfig.id].avg).to.be(football); + }); + }); + + describe('::ensureIds', function() { + it('accepts an array of objects and assigns ids to them', function() { + const objs = [{}, {}, {}, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).to.have.property('id', '1'); + expect(objs[1]).to.have.property('id', '2'); + expect(objs[2]).to.have.property('id', '3'); + expect(objs[3]).to.have.property('id', '4'); + }); + + it('assigns ids relative to the other only item in the list', function() { + const objs = [{ id: '100' }, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).to.have.property('id', '100'); + expect(objs[1]).to.have.property('id', '101'); + }); + + it('assigns ids relative to the other items in the list', function() { + const objs = [{ id: '100' }, { id: '200' }, { id: '500' }, { id: '350' }, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).to.have.property('id', '100'); + expect(objs[1]).to.have.property('id', '200'); + expect(objs[2]).to.have.property('id', '500'); + expect(objs[3]).to.have.property('id', '350'); + expect(objs[4]).to.have.property('id', '501'); + }); + + it('uses ::nextId to get the starting value', function() { + sinon.stub(AggConfig, 'nextId').returns(534); + const objs = AggConfig.ensureIds([{}]); + AggConfig.nextId.restore(); + expect(objs[0]).to.have.property('id', '534'); + }); + + it('only calls ::nextId once', function() { + const start = 420; + sinon.stub(AggConfig, 'nextId').returns(start); + const objs = AggConfig.ensureIds([{}, {}, {}, {}, {}, {}, {}]); + + expect(AggConfig.nextId).to.have.property('callCount', 1); + + AggConfig.nextId.restore(); + objs.forEach(function(obj, i) { + expect(obj).to.have.property('id', String(start + i)); + }); + }); + }); + + describe('::nextId', function() { + it('accepts a list of objects and picks the next id', function() { + const next = AggConfig.nextId([{ id: 100 }, { id: 500 }]); + expect(next).to.be(501); + }); + + it('handles an empty list', function() { + const next = AggConfig.nextId([]); + expect(next).to.be(1); + }); + + it('fails when the list is not defined', function() { + expect(function() { + AggConfig.nextId(); + }).to.throwError(); + }); + }); + + describe('#toJsonDataEquals', function() { + const testsIdentical = [ + { + type: 'metric', + aggs: [ + { + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + }, + { + type: 'histogram', + aggs: [ + { + type: 'avg', + schema: 'metric', + }, + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }, + ]; + + testsIdentical.forEach((visConfig, index) => { + it(`identical aggregations (${index})`, function() { + const vis1 = new visualizationsStart.Vis(indexPattern, visConfig); + const vis2 = new visualizationsStart.Vis(indexPattern, visConfig); + expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(true); + }); + }); + + const testsIdenticalDifferentOrder = [ + { + config1: { + type: 'histogram', + aggs: [ + { + type: 'avg', + schema: 'metric', + }, + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }, + config2: { + type: 'histogram', + aggs: [ + { + schema: 'metric', + type: 'avg', + }, + { + schema: 'segment', + type: 'date_histogram', + }, + ], + }, + }, + ]; + + testsIdenticalDifferentOrder.forEach((test, index) => { + it(`identical aggregations (${index}) - init json is in different order`, function() { + const vis1 = new visualizationsStart.Vis(indexPattern, test.config1); + const vis2 = new visualizationsStart.Vis(indexPattern, test.config2); + expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(true); + }); + }); + + const testsDifferent = [ + { + config1: { + type: 'histogram', + aggs: [ + { + type: 'avg', + schema: 'metric', + }, + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }, + config2: { + type: 'histogram', + aggs: [ + { + type: 'max', + schema: 'metric', + }, + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }, + }, + { + config1: { + type: 'metric', + aggs: [ + { + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + }, + config2: { + type: 'metric', + aggs: [ + { + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }, + }, + ]; + + testsDifferent.forEach((test, index) => { + it(`different aggregations (${index})`, function() { + const vis1 = new visualizationsStart.Vis(indexPattern, test.config1); + const vis2 = new visualizationsStart.Vis(indexPattern, test.config2); + expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(false); + }); + }); + }); + + describe('#toJSON', function() { + it('includes the aggs id, params, type and schema', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }); + + const aggConfig = vis.aggs.byName('date_histogram')[0]; + expect(aggConfig.id).to.be('1'); + expect(aggConfig.params).to.be.an('object'); + expect(aggConfig.type) + .to.be.an(AggType) + .and.have.property('name', 'date_histogram'); + expect(aggConfig.schema) + .to.be.an('object') + .and.have.property('name', 'segment'); + + const state = aggConfig.toJSON(); + expect(state).to.have.property('id', '1'); + expect(state.params).to.be.an('object'); + expect(state).to.have.property('type', 'date_histogram'); + expect(state).to.have.property('schema', 'segment'); + }); + + it('test serialization order is identical (for visual consistency)', function() { + const vis1 = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + }, + ], + }); + const vis2 = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + schema: 'segment', + type: 'date_histogram', + }, + ], + }); + + //this relies on the assumption that js-engines consistently loop over properties in insertion order. + //most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. + expect(JSON.stringify(vis1.aggs.aggs) === JSON.stringify(vis2.aggs.aggs)).to.be(true); + }); + }); + + describe('#makeLabel', function() { + it('uses the custom label if it is defined', function() { + const vis = new visualizationsStart.Vis(indexPattern, {}); + const aggConfig = vis.aggs.aggs[0]; + aggConfig.params.customLabel = 'Custom label'; + const label = aggConfig.makeLabel(); + expect(label).to.be(aggConfig.params.customLabel); + }); + it('default label should be "Count"', function() { + const vis = new visualizationsStart.Vis(indexPattern, {}); + const aggConfig = vis.aggs.aggs[0]; + const label = aggConfig.makeLabel(); + expect(label).to.be('Count'); + }); + it('default label should be "Percentage of Count" when percentageMode is set to true', function() { + const vis = new visualizationsStart.Vis(indexPattern, {}); + const aggConfig = vis.aggs.aggs[0]; + const label = aggConfig.makeLabel(true); + expect(label).to.be('Percentage of Count'); + }); + it('empty label if the visualizationsStart.Vis type is not defined', function() { + const vis = new visualizationsStart.Vis(indexPattern, {}); + const aggConfig = vis.aggs.aggs[0]; + aggConfig.type = undefined; + const label = aggConfig.makeLabel(); + expect(label).to.be(''); + }); + }); + + describe('#fieldFormatter - custom getFormat handler', function() { + it('returns formatter from getFormat handler', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'metric', + aggs: [ + { + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + }); + + const fieldFormatter = vis.aggs.aggs[0].fieldFormatter(); + + expect(fieldFormatter).to.be.defined; + expect(fieldFormatter('text')).to.be('text'); + }); + }); + + describe('#fieldFormatter - no custom getFormat handler', function() { + const visStateAggWithoutCustomGetFormat = { + aggs: [ + { + type: 'histogram', + schema: 'bucket', + params: { field: 'bytes' }, + }, + ], + }; + let vis; + + beforeEach(function() { + vis = new visualizationsStart.Vis(indexPattern, visStateAggWithoutCustomGetFormat); + }); + + it("returns the field's formatter", function() { + expect(vis.aggs.aggs[0].fieldFormatter().toString()).to.be( + vis.aggs.aggs[0] + .getField() + .format.getConverterFor() + .toString() + ); + }); + + it('returns the string format if the field does not have a format', function() { + const agg = vis.aggs.aggs[0]; + agg.params.field = { type: 'number', format: null }; + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).to.be.defined; + expect(fieldFormatter('text')).to.be('text'); + }); + + it('returns the string format if their is no field', function() { + const agg = vis.aggs.aggs[0]; + delete agg.params.field; + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).to.be.defined; + expect(fieldFormatter('text')).to.be('text'); + }); + + it('returns the html converter if "html" is passed in', function() { + const field = indexPattern.fields.getByName('bytes'); + expect(vis.aggs.aggs[0].fieldFormatter('html').toString()).to.be( + field.format.getConverterFor('html').toString() + ); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/__tests__/_agg_configs.js b/src/legacy/ui/public/vis/__tests__/_agg_configs.js new file mode 100644 index 00000000000000..172523ec50c8b0 --- /dev/null +++ b/src/legacy/ui/public/vis/__tests__/_agg_configs.js @@ -0,0 +1,420 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import sinon from 'sinon'; +import expect from '@kbn/expect'; +import ngMock from 'ng_mock'; +import { AggConfig, AggConfigs, AggGroupNames, Schemas } from '../../agg_types'; +import { start as visualizationsStart } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; + +describe('AggConfigs', function() { + let indexPattern; + + beforeEach(ngMock.module('kibana')); + beforeEach( + ngMock.inject(function(Private) { + // load main deps + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + }) + ); + + describe('constructor', function() { + it('handles passing just a vis', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [], + }); + + const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); + expect(ac.aggs).to.have.length(1); + }); + + it('converts configStates into AggConfig objects if they are not already', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [], + }); + + const ac = new AggConfigs( + vis.indexPattern, + [ + { + type: 'date_histogram', + schema: 'segment', + }, + new AggConfig(vis.aggs, { + type: 'terms', + schema: 'split', + }), + ], + vis.type.schemas.all + ); + + expect(ac.aggs).to.have.length(3); + }); + + it('attempts to ensure that all states have an id', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [], + }); + + const states = [ + { + type: 'date_histogram', + schema: 'segment', + }, + { + type: 'terms', + schema: 'split', + }, + ]; + + const spy = sinon.spy(AggConfig, 'ensureIds'); + new AggConfigs(vis.indexPattern, states, vis.type.schemas.all); + expect(spy.callCount).to.be(1); + expect(spy.firstCall.args[0]).to.be(states); + AggConfig.ensureIds.restore(); + }); + + describe('defaults', function() { + let vis; + beforeEach(function() { + vis = { + indexPattern: indexPattern, + type: { + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: 'Simple', + min: 1, + max: 2, + defaults: [ + { schema: 'metric', type: 'count' }, + { schema: 'metric', type: 'avg' }, + { schema: 'metric', type: 'sum' }, + ], + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: 'Example', + min: 0, + max: 1, + defaults: [ + { schema: 'segment', type: 'terms' }, + { schema: 'segment', type: 'filters' }, + ], + }, + ]), + }, + }; + }); + + it('should only set the number of defaults defined by the max', function() { + const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); + expect(ac.bySchemaName('metric')).to.have.length(2); + }); + + it('should set the defaults defined in the schema when none exist', function() { + const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); + expect(ac.aggs).to.have.length(3); + }); + + it('should NOT set the defaults defined in the schema when some exist', function() { + const ac = new AggConfigs( + vis.indexPattern, + [{ schema: 'segment', type: 'date_histogram' }], + vis.type.schemas.all + ); + expect(ac.aggs).to.have.length(3); + expect(ac.bySchemaName('segment')[0].type.name).to.equal('date_histogram'); + }); + }); + }); + + describe('#getRequestAggs', function() { + it('performs a stable sort, but moves metrics to the bottom', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'avg', schema: 'metric' }, + { type: 'terms', schema: 'split' }, + { type: 'histogram', schema: 'split' }, + { type: 'sum', schema: 'metric' }, + { type: 'date_histogram', schema: 'segment' }, + { type: 'filters', schema: 'split' }, + { type: 'percentiles', schema: 'metric' }, + ], + }); + + const sorted = vis.aggs.getRequestAggs(); + const aggs = _.indexBy(vis.aggs.aggs, function(agg) { + return agg.type.name; + }); + + expect(sorted.shift()).to.be(aggs.terms); + expect(sorted.shift()).to.be(aggs.histogram); + expect(sorted.shift()).to.be(aggs.date_histogram); + expect(sorted.shift()).to.be(aggs.filters); + expect(sorted.shift()).to.be(aggs.avg); + expect(sorted.shift()).to.be(aggs.sum); + expect(sorted.shift()).to.be(aggs.percentiles); + expect(sorted).to.have.length(0); + }); + }); + + describe('#getResponseAggs', function() { + it('returns all request aggs for basic aggs', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', schema: 'split' }, + { type: 'date_histogram', schema: 'segment' }, + { type: 'count', schema: 'metric' }, + ], + }); + + const sorted = vis.aggs.getResponseAggs(); + const aggs = _.indexBy(vis.aggs.aggs, function(agg) { + return agg.type.name; + }); + + expect(sorted.shift()).to.be(aggs.terms); + expect(sorted.shift()).to.be(aggs.date_histogram); + expect(sorted.shift()).to.be(aggs.count); + expect(sorted).to.have.length(0); + }); + + it('expands aggs that have multiple responses', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', schema: 'split' }, + { type: 'date_histogram', schema: 'segment' }, + { type: 'percentiles', schema: 'metric', params: { percents: [1, 2, 3] } }, + ], + }); + + const sorted = vis.aggs.getResponseAggs(); + const aggs = _.indexBy(vis.aggs.aggs, function(agg) { + return agg.type.name; + }); + + expect(sorted.shift()).to.be(aggs.terms); + expect(sorted.shift()).to.be(aggs.date_histogram); + expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 1); + expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 2); + expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 3); + expect(sorted).to.have.length(0); + }); + }); + + describe('#toDsl', function() { + it('uses the sorted aggs', function() { + const vis = new visualizationsStart.Vis(indexPattern, { type: 'histogram' }); + sinon.spy(vis.aggs, 'getRequestAggs'); + vis.aggs.toDsl(); + expect(vis.aggs.getRequestAggs).to.have.property('callCount', 1); + }); + + it('calls aggConfig#toDsl() on each aggConfig and compiles the nested output', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'date_histogram', schema: 'segment' }, + { type: 'filters', schema: 'split' }, + ], + }); + + const aggInfos = vis.aggs.aggs.map(function(aggConfig) { + const football = {}; + + sinon.stub(aggConfig, 'toDsl').returns(football); + + return { + id: aggConfig.id, + football: football, + }; + }); + + (function recurse(lvl) { + const info = aggInfos.shift(); + + expect(lvl).to.have.property(info.id); + expect(lvl[info.id]).to.be(info.football); + + if (lvl[info.id].aggs) { + return recurse(lvl[info.id].aggs); + } + })(vis.aggs.toDsl()); + + expect(aggInfos).to.have.length(1); + }); + + it("skips aggs that don't have a dsl representation", function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + params: { field: '@timestamp', interval: '10s' }, + }, + { type: 'count', schema: 'metric' }, + ], + }); + + const dsl = vis.aggs.toDsl(); + const histo = vis.aggs.byName('date_histogram')[0]; + const count = vis.aggs.byName('count')[0]; + + expect(dsl).to.have.property(histo.id); + expect(dsl[histo.id]).to.be.an('object'); + expect(dsl[histo.id]).to.not.have.property('aggs'); + expect(dsl).to.not.have.property(count.id); + }); + + it('writes multiple metric aggregations at the same level', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment', + params: { field: '@timestamp', interval: '10s' }, + }, + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { type: 'min', schema: 'metric', params: { field: 'bytes' } }, + { type: 'max', schema: 'metric', params: { field: 'bytes' } }, + ], + }); + + const dsl = vis.aggs.toDsl(); + + const histo = vis.aggs.byName('date_histogram')[0]; + const metrics = vis.aggs.bySchemaGroup('metrics'); + + expect(dsl).to.have.property(histo.id); + expect(dsl[histo.id]).to.be.an('object'); + expect(dsl[histo.id]).to.have.property('aggs'); + + metrics.forEach(function(metric) { + expect(dsl[histo.id].aggs).to.have.property(metric.id); + expect(dsl[histo.id].aggs[metric.id]).to.not.have.property('aggs'); + }); + }); + + it('writes multiple metric aggregations at every level if the vis is hierarchical', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', schema: 'segment', params: { field: 'ip', orderBy: 1 } }, + { type: 'terms', schema: 'segment', params: { field: 'extension', orderBy: 1 } }, + { id: 1, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { type: 'min', schema: 'metric', params: { field: 'bytes' } }, + { type: 'max', schema: 'metric', params: { field: 'bytes' } }, + ], + }); + vis.isHierarchical = _.constant(true); + + const topLevelDsl = vis.aggs.toDsl(vis.isHierarchical()); + const buckets = vis.aggs.bySchemaGroup('buckets'); + const metrics = vis.aggs.bySchemaGroup('metrics'); + + (function checkLevel(dsl) { + const bucket = buckets.shift(); + expect(dsl).to.have.property(bucket.id); + + expect(dsl[bucket.id]).to.be.an('object'); + expect(dsl[bucket.id]).to.have.property('aggs'); + + metrics.forEach(function(metric) { + expect(dsl[bucket.id].aggs).to.have.property(metric.id); + expect(dsl[bucket.id].aggs[metric.id]).to.not.have.property('aggs'); + }); + + if (buckets.length) { + checkLevel(dsl[bucket.id].aggs); + } + })(topLevelDsl); + }); + + it('adds the parent aggs of nested metrics at every level if the vis is hierarchical', function() { + const vis = new visualizationsStart.Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + id: '1', + type: 'avg_bucket', + schema: 'metric', + params: { + customBucket: { + id: '1-bucket', + type: 'date_histogram', + schema: 'bucketAgg', + params: { + field: '@timestamp', + interval: '10s', + }, + }, + customMetric: { + id: '1-metric', + type: 'count', + schema: 'metricAgg', + params: {}, + }, + }, + }, + { + id: '2', + type: 'terms', + schema: 'bucket', + params: { + field: 'geo.src', + }, + }, + { + id: '3', + type: 'terms', + schema: 'bucket', + params: { + field: 'machine.os', + }, + }, + ], + }); + vis.isHierarchical = _.constant(true); + + const topLevelDsl = vis.aggs.toDsl(vis.isHierarchical())['2']; + expect(topLevelDsl.aggs).to.have.keys(['1', '1-bucket']); + expect(topLevelDsl.aggs['1'].avg_bucket).to.have.property('buckets_path', '1-bucket>_count'); + expect(topLevelDsl.aggs['3'].aggs).to.have.keys(['1', '1-bucket']); + expect(topLevelDsl.aggs['3'].aggs['1'].avg_bucket).to.have.property( + 'buckets_path', + '1-bucket>_count' + ); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts b/src/legacy/ui/public/vis/__tests__/index.js similarity index 86% rename from src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts rename to src/legacy/ui/public/vis/__tests__/index.js index 131f921586144c..46074f2c5197b7 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts +++ b/src/legacy/ui/public/vis/__tests__/index.js @@ -17,5 +17,5 @@ * under the License. */ -export { mockAggTypesRegistry } from './mock_agg_types_registry'; -export { mockDataServices } from './mock_data_services'; +import './_agg_config'; +import './_agg_configs'; diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts deleted file mode 100644 index bc38374e147cf6..00000000000000 --- a/src/plugins/data/common/field_formats/mocks.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { FieldFormat, IFieldFormatsRegistry } from '.'; - -const fieldFormatMock = ({ - convert: jest.fn(), - getConverterFor: jest.fn(), - getParamDefaults: jest.fn(), - param: jest.fn(), - params: jest.fn(), - toJSON: jest.fn(), - type: jest.fn(), - setupContentType: jest.fn(), -} as unknown) as FieldFormat; - -export const fieldFormatsMock: IFieldFormatsRegistry = { - getByFieldType: jest.fn(), - getDefaultConfig: jest.fn(), - getDefaultInstance: jest.fn().mockImplementation(() => fieldFormatMock) as any, - getDefaultInstanceCacheResolver: jest.fn(), - getDefaultInstancePlain: jest.fn(), - getDefaultType: jest.fn(), - getDefaultTypeName: jest.fn(), - getInstance: jest.fn() as any, - getType: jest.fn(), - getTypeNameByEsTypes: jest.fn(), - init: jest.fn(), - register: jest.fn(), - parseDefaultTypeMap: jest.fn(), - deserialize: jest.fn(), - getTypeWithoutMetaParams: jest.fn(), -}; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 27de3b5a29bfd0..6a0a33096eaac1 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,9 +16,13 @@ * specific language governing permissions and limitations * under the License. */ - -import { Plugin, DataPublicPluginSetup, DataPublicPluginStart, IndexPatternsContract } from '.'; -import { fieldFormatsMock } from '../common/field_formats/mocks'; +import { + Plugin, + DataPublicPluginSetup, + DataPublicPluginStart, + IndexPatternsContract, + IFieldFormatsRegistry, +} from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -31,6 +35,24 @@ const autocompleteMock: any = { hasQuerySuggestions: jest.fn(), }; +const fieldFormatsMock: IFieldFormatsRegistry = { + getByFieldType: jest.fn(), + getDefaultConfig: jest.fn(), + getDefaultInstance: jest.fn() as any, + getDefaultInstanceCacheResolver: jest.fn(), + getDefaultInstancePlain: jest.fn(), + getDefaultType: jest.fn(), + getDefaultTypeName: jest.fn(), + getInstance: jest.fn() as any, + getType: jest.fn(), + getTypeNameByEsTypes: jest.fn(), + init: jest.fn(), + register: jest.fn(), + parseDefaultTypeMap: jest.fn(), + deserialize: jest.fn(), + getTypeWithoutMetaParams: jest.fn(), +}; + const createSetupContract = (): Setup => { const querySetupMock = queryServiceMock.createSetupContract(); const setupContract = { diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index 700bea741bd6ae..fd72158012de6f 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -17,6 +17,25 @@ * under the License. */ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"), you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import { ISearchSource } from './search_source'; export const searchSourceMock: MockedKeys = { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx index 2e1645c816140c..ef4b5f6d7b834f 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx @@ -17,6 +17,10 @@ jest.mock('ui/new_platform'); // mock away actual dependencies to prevent all of it being loaded jest.mock('../../../../../../src/legacy/core_plugins/interpreter/public/registries', () => {}); +jest.mock('../../../../../../src/legacy/core_plugins/data/public/legacy', () => ({ + start: {}, + setup: {}, +})); jest.mock('./embeddable/embeddable_factory', () => ({ EmbeddableFactory: class Mock {}, })); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index da04b970a494b8..c883983f8cf010 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -2803,7 +2803,7 @@ tr:hover .c3:focus::before {
- - -
- - -
- - - - - - + -
- - - - - - - - + } + > + - Cu0n232QMyvNtzb75j - - - - + + - - + > + + + + - - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
+ + + + + + + +
+ + + + + + + + + + + + + + @@ -3087,7 +3105,7 @@ tr:hover .c3:focus::before {
- - -
- - -
- - - - - - + -
- - - - - - - - + } + > + - files - - - - + + - - + > + + + + - - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
+ + + + + + + +
+ + + + + + + + + + + + + + @@ -3371,7 +3407,7 @@ tr:hover .c3:focus::before {
- - -
- - -
- - - - - - + -
- - - - - - - - + } + > + - sha1: fa5195a... - - - - + + - - + > + + + + - - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
+ + + + + + + +
+ + + + + + + + + + + + + + @@ -3655,7 +3709,7 @@ tr:hover .c3:focus::before {
- - -
- - -
- - - - - - + -
- - - - - - - - + } + > + - md5: f7653f1... - - - - + + - - + > + + + + - - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
+ + + + + + + +
+ + + + + + + + + + + + + + From 8a8af5a57c5fa33e02da8e8d3db652f5b716375e Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 27 Feb 2020 23:37:39 -0700 Subject: [PATCH 26/28] skip flaky suite (#58662) --- .../bfetch/common/buffer/tests/timed_item_buffer.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts index c1c6a8f187a44c..e1640927c4ead3 100644 --- a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts +++ b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts @@ -20,7 +20,8 @@ import { TimedItemBuffer } from '../timed_item_buffer'; import { runItemBufferTests } from './run_item_buffer_tests'; -describe('TimedItemBuffer', () => { +// FLAKY: https://github.com/elastic/kibana/issues/58662 +describe.skip('TimedItemBuffer', () => { runItemBufferTests(TimedItemBuffer); test('does not do unnecessary flushes', async () => { From 913afb3bada2bd75f045ac7d8ec7483c774e2c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Fri, 28 Feb 2020 08:54:50 +0100 Subject: [PATCH 27/28] Update APM readme --- x-pack/legacy/plugins/apm/readme.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index a513249c296db3..0edcdc279815cd 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -74,10 +74,14 @@ node scripts/jest.js plugins/apm --updateSnapshot ### Functional tests **Start server** -`node scripts/functional_tests_server --config x-pack/test/functional/config.js` +``` +node scripts/functional_tests_server --config x-pack/test/functional/config.js +``` **Run tests** -`node scripts/functional_test_runner --config x-pack/test/functional/config.js --grep='APM specs'` +``` +node scripts/functional_test_runner --config x-pack/test/functional/config.js --grep='APM specs' +``` APM tests are located in `x-pack/test/functional/apps/apm`. For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) @@ -85,10 +89,14 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) ### API integration tests **Start server** -`node scripts/functional_tests_server --config x-pack/test/api_integration/config.js` +``` +node scripts/functional_tests_server --config x-pack/test/api_integration/config.js +``` **Run tests** -`node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='APM specs'` +``` +node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='APM specs' +``` APM tests are located in `x-pack/test/api_integration/apis/apm`. For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) From ce45647ea2212f5cc42147e9b5bb82cb962e81ca Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Fri, 28 Feb 2020 09:20:51 +0100 Subject: [PATCH 28/28] block SO setup API calls after startup (#58718) --- ...-plugin-server.savedobjectsservicesetup.md | 2 ++ src/core/MIGRATION_EXAMPLES.md | 8 ++++- .../saved_objects_service.test.ts | 30 +++++++++++++++++++ .../saved_objects/saved_objects_service.ts | 15 ++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md index b6f2e7320c48ad..963c4bbeb55153 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -16,6 +16,8 @@ export interface SavedObjectsServiceSetup When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. +All the setup APIs will throw if called after the service has started, and therefor cannot be used from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated. + ## Example 1 diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index def83ba177fc9f..2953edb535f47a 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -917,4 +917,10 @@ Would be converted to: ```typescript const migration: SavedObjectMigrationFn = (doc, { log }) => {...} -``` \ No newline at end of file +``` + +### Remarks + +The `registerType` API will throw if called after the service has started, and therefor cannot be used from +legacy plugin code. Legacy plugins should use the legacy savedObjects service and the legacy way to register +saved object types until migrated. \ No newline at end of file diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index a1e2c1e8dbf263..554acf8d43dcb9 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -232,6 +232,36 @@ describe('SavedObjectsService', () => { expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); }); + it('throws when calling setup APIs once started', async () => { + const coreContext = createCoreContext({ skipMigration: false }); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + await soService.start({}); + + expect(() => { + setup.setClientFactoryProvider(jest.fn()); + }).toThrowErrorMatchingInlineSnapshot( + `"cannot call \`setClientFactoryProvider\` after service startup."` + ); + + expect(() => { + setup.addClientWrapper(0, 'dummy', jest.fn()); + }).toThrowErrorMatchingInlineSnapshot( + `"cannot call \`addClientWrapper\` after service startup."` + ); + + expect(() => { + setup.registerType({ + name: 'someType', + hidden: false, + namespaceAgnostic: false, + mappings: { properties: {} }, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"cannot call \`registerType\` after service startup."` + ); + }); + describe('#getTypeRegistry', () => { it('returns the internal type registry of the service', async () => { const coreContext = createCoreContext({ skipMigration: false }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index da8f7ab96d6891..d5a9f474209711 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -61,6 +61,9 @@ import { registerRoutes } from './routes'; * the factory provided to `setClientFactory` and wrapped by all wrappers * registered through `addClientWrapper`. * + * All the setup APIs will throw if called after the service has started, and therefor cannot be used + * from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated. + * * @example * ```ts * import { SavedObjectsClient, CoreSetup } from 'src/core/server'; @@ -275,6 +278,7 @@ export class SavedObjectsService private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); private validations: PropertyValidators = {}; + private started = false; constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('savedobjects-service'); @@ -316,12 +320,18 @@ export class SavedObjectsService return { setClientFactoryProvider: provider => { + if (this.started) { + throw new Error('cannot call `setClientFactoryProvider` after service startup.'); + } if (this.clientFactoryProvider) { throw new Error('custom client factory is already set, and can only be set once'); } this.clientFactoryProvider = provider; }, addClientWrapper: (priority, id, factory) => { + if (this.started) { + throw new Error('cannot call `addClientWrapper` after service startup.'); + } this.clientFactoryWrappers.push({ priority, id, @@ -329,6 +339,9 @@ export class SavedObjectsService }); }, registerType: type => { + if (this.started) { + throw new Error('cannot call `registerType` after service startup.'); + } this.typeRegistry.registerType(type); }, }; @@ -415,6 +428,8 @@ export class SavedObjectsService clientProvider.addClientWrapperFactory(priority, id, factory); }); + this.started = true; + return { migrator, clientProvider,
+ + ); + + expect(props.registerProvider.mock.calls.length).toEqual(0); + }); + + it('calls registerProvider when isDragging', () => { + mount( + +
+ + ); + + expect(props.registerProvider.mock.calls.length).toEqual(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx index 7d84403b87f8d0..4b80b9fff2740e 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { createContext, useContext, useEffect } from 'react'; +import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; import { Draggable, DraggableProvided, DraggableStateSnapshot, Droppable, } from 'react-beautiful-dnd'; -import { connect, ConnectedProps } from 'react-redux'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -47,34 +47,50 @@ const ProviderContentWrapper = styled.span` } `; +type RenderFunctionProp = ( + props: DataProvider, + provided: DraggableProvided, + state: DraggableStateSnapshot +) => React.ReactNode; + interface OwnProps { dataProvider: DataProvider; inline?: boolean; - render: ( - props: DataProvider, - provided: DraggableProvided, - state: DraggableStateSnapshot - ) => React.ReactNode; + render: RenderFunctionProp; truncate?: boolean; } -type Props = OwnProps & PropsFromRedux; +type Props = OwnProps; /** * Wraps a draggable component to handle registration / unregistration of the * data provider associated with the item being dropped */ -const DraggableWrapperComponent = React.memo( - ({ dataProvider, registerProvider, render, truncate, unRegisterProvider }) => { +export const DraggableWrapper = React.memo( + ({ dataProvider, render, truncate }) => { + const [providerRegistered, setProviderRegistered] = useState(false); + const dispatch = useDispatch(); const usePortal = useDraggablePortalContext(); - useEffect(() => { - registerProvider!({ provider: dataProvider }); - return () => { - unRegisterProvider!({ id: dataProvider.id }); - }; - }, []); + const registerProvider = useCallback(() => { + if (!providerRegistered) { + dispatch(dragAndDropActions.registerProvider({ provider: dataProvider })); + setProviderRegistered(true); + } + }, [dispatch, providerRegistered, dataProvider]); + + const unRegisterProvider = useCallback( + () => dispatch(dragAndDropActions.unRegisterProvider({ id: dataProvider.id })), + [dispatch, dataProvider] + ); + + useEffect( + () => () => { + unRegisterProvider(); + }, + [] + ); return ( @@ -87,13 +103,18 @@ const DraggableWrapperComponent = React.memo( key={getDraggableId(dataProvider.id)} > {(provided, snapshot) => ( - + ( ); }, - (prevProps, nextProps) => { - return ( - deepEqual(prevProps.dataProvider, nextProps.dataProvider) && - prevProps.render !== nextProps.render && - prevProps.truncate === nextProps.truncate - ); - } + (prevProps, nextProps) => + deepEqual(prevProps.dataProvider, nextProps.dataProvider) && + prevProps.render !== nextProps.render && + prevProps.truncate === nextProps.truncate ); -DraggableWrapperComponent.displayName = 'DraggableWrapperComponent'; - -const mapDispatchToProps = { - registerProvider: dragAndDropActions.registerProvider, - unRegisterProvider: dragAndDropActions.unRegisterProvider, -}; - -const connector = connect(null, mapDispatchToProps); - -type PropsFromRedux = ConnectedProps; - -export const DraggableWrapper = connector(DraggableWrapperComponent); - DraggableWrapper.displayName = 'DraggableWrapper'; /** @@ -150,8 +155,24 @@ DraggableWrapper.displayName = 'DraggableWrapper'; * * See: https://github.com/atlassian/react-beautiful-dnd/issues/499 */ -const ConditionalPortal = React.memo<{ children: React.ReactNode; usePortal: boolean }>( - ({ children, usePortal }) => (usePortal ? {children} : <>{children}) + +interface ConditionalPortalProps { + children: React.ReactNode; + usePortal: boolean; + isDragging: boolean; + registerProvider: () => void; +} + +export const ConditionalPortal = React.memo( + ({ children, usePortal, registerProvider, isDragging }) => { + useEffect(() => { + if (isDragging) { + registerProvider(); + } + }, [isDragging, registerProvider]); + + return usePortal ? {children} : <>{children}; + } ); ConditionalPortal.displayName = 'ConditionalPortal'; From d474ccf244d22b8abf7df1be3e96b36b715281f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Thu, 27 Feb 2020 22:55:02 +0100 Subject: [PATCH 16/28] [APM] Fix timeout in APM setup (#58727) * [APM] Fix timeout in APM setup * Update plugin.ts --- src/plugins/apm_oss/server/index.ts | 2 +- src/plugins/apm_oss/server/plugin.ts | 6 ++- .../get_dynamic_index_pattern.ts | 3 +- .../create_agent_config_index.ts | 9 ++-- x-pack/plugins/apm/server/plugin.ts | 43 ++++++++----------- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts index 801140694c1394..95a4ae4519bc9a 100644 --- a/src/plugins/apm_oss/server/index.ts +++ b/src/plugins/apm_oss/server/index.ts @@ -38,4 +38,4 @@ export function plugin(initializerContext: PluginInitializerContext) { export type APMOSSConfig = TypeOf; -export { APMOSSPlugin as Plugin }; +export { APMOSSPluginSetup } from './plugin'; diff --git a/src/plugins/apm_oss/server/plugin.ts b/src/plugins/apm_oss/server/plugin.ts index 2708f7729482bd..9b14d19da90c2e 100644 --- a/src/plugins/apm_oss/server/plugin.ts +++ b/src/plugins/apm_oss/server/plugin.ts @@ -20,7 +20,7 @@ import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server'; import { Observable } from 'rxjs'; import { APMOSSConfig } from './'; -export class APMOSSPlugin implements Plugin<{ config$: Observable }> { +export class APMOSSPlugin implements Plugin { constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; } @@ -36,3 +36,7 @@ export class APMOSSPlugin implements Plugin<{ config$: Observable start() {} stop() {} } + +export interface APMOSSPluginSetup { + config$: Observable; +} diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index b1e4906317f815..a7476bf564a16b 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -62,8 +62,7 @@ export const getDynamicIndexPattern = async ({ cache.set(CACHE_KEY, undefined); const notExists = e.output?.statusCode === 404; if (notExists) { - // eslint-disable-next-line no-console - console.error( + context.logger.error( `Could not get dynamic index pattern because indices "${indexPatternTitle}" don't exist` ); return; diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index af2d2a13eaa2fa..8cfb7e7edb4c69 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -4,17 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient } from 'src/core/server'; +import { IClusterClient, Logger } from 'src/core/server'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; import { APMConfig } from '../../..'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; export async function createApmAgentConfigurationIndex({ esClient, - config + config, + logger }: { esClient: IClusterClient; config: APMConfig; + logger: Logger; }) { try { const index = getApmIndicesConfig(config).apmAgentConfigurationIndex; @@ -32,8 +34,7 @@ export async function createApmAgentConfigurationIndex({ ); } } catch (e) { - // eslint-disable-next-line no-console - console.error('Could not create APM Agent configuration:', e.message); + logger.error(`Could not create APM Agent configuration: ${e.message}`); } } diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index adc80cb43620bf..773f0d4e6fac56 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -5,12 +5,12 @@ */ import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server'; import { Observable, combineLatest, AsyncSubject } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { Server } from 'hapi'; import { once } from 'lodash'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server'; import { makeApmUsageCollector } from './lib/apm_telemetry'; -import { Plugin as APMOSSPlugin } from '../../../../src/plugins/apm_oss/server'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; import { createApmApi } from './routes/create_apm_api'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; @@ -33,26 +33,23 @@ export interface APMPluginContract { export class APMPlugin implements Plugin { legacySetup$: AsyncSubject; - currentConfig: APMConfig; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; this.legacySetup$ = new AsyncSubject(); - this.currentConfig = {} as APMConfig; } public async setup( core: CoreSetup, plugins: { - apm_oss: APMOSSPlugin extends Plugin ? TSetup : never; + apm_oss: APMOSSPluginSetup; home: HomeServerPluginSetup; licensing: LicensingPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; } ) { - const config$ = this.initContext.config.create(); const logger = this.initContext.logger.get('apm'); - + const config$ = this.initContext.config.create(); const mergedConfig$ = combineLatest(plugins.apm_oss.config$, config$).pipe( map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig)) ); @@ -61,28 +58,26 @@ export class APMPlugin implements Plugin { createApmApi().init(core, { config$: mergedConfig$, logger, __LEGACY }); }); - await new Promise(resolve => { - mergedConfig$.subscribe(async config => { - this.currentConfig = config; - await createApmAgentConfigurationIndex({ - esClient: core.elasticsearch.dataClient, - config - }); - resolve(); - }); + const currentConfig = await mergedConfig$.pipe(take(1)).toPromise(); + + // create agent configuration index without blocking setup lifecycle + createApmAgentConfigurationIndex({ + esClient: core.elasticsearch.dataClient, + config: currentConfig, + logger }); plugins.home.tutorials.registerTutorial( tutorialProvider({ - isEnabled: this.currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], + isEnabled: currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: currentConfig['apm_oss.indexPattern'], cloud: plugins.cloud, indices: { - errorIndices: this.currentConfig['apm_oss.errorIndices'], - metricsIndices: this.currentConfig['apm_oss.metricsIndices'], - onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: this.currentConfig['apm_oss.transactionIndices'] + errorIndices: currentConfig['apm_oss.errorIndices'], + metricsIndices: currentConfig['apm_oss.metricsIndices'], + onboardingIndices: currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: currentConfig['apm_oss.transactionIndices'] } }) ); @@ -108,7 +103,7 @@ export class APMPlugin implements Plugin { getApmIndices: async () => getApmIndices({ savedObjectsClient: await getInternalSavedObjectsClient(core), - config: this.currentConfig + config: currentConfig }) }; } From 07c22f13b73ee29ade817d8abb8f73823df76899 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 27 Feb 2020 15:23:54 -0700 Subject: [PATCH 17/28] skip flaky suite (#58785) --- x-pack/test/api_integration/apis/security/privileges.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 7b1984222404bc..81cffaac07285b 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -9,7 +9,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe('Privileges', () => { + // FLAKY: https://github.com/elastic/kibana/issues/58785 + describe.skip('Privileges', () => { describe('GET /api/security/privileges', () => { it('should return a privilege map with all known privileges, without actions', async () => { await supertest From 467280232abf822d258aef5459e91e0375124485 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 27 Feb 2020 17:19:35 -0700 Subject: [PATCH 18/28] Revert "[SIEM] Fix Timeline registerProvider to be called only when it's needed (#58051)" This reverts commit 2a03dffdad6c1dbeaf9a541c4ea0fb84183a8ca8. --- .../drag_and_drop/draggable_wrapper.test.tsx | 31 +----- .../drag_and_drop/draggable_wrapper.tsx | 99 ++++++++----------- 2 files changed, 40 insertions(+), 90 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx index d34f4cce9fea41..92adc1a9adb7a2 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx @@ -12,7 +12,7 @@ import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; import { mockDataProviders } from '../timeline/data_providers/mock/mock_data_providers'; import { DragDropContextWrapper } from './drag_drop_context_wrapper'; -import { DraggableWrapper, ConditionalPortal } from './draggable_wrapper'; +import { DraggableWrapper } from './draggable_wrapper'; import { useMountAppended } from '../../utils/use_mount_appended'; describe('DraggableWrapper', () => { @@ -84,32 +84,3 @@ describe('DraggableWrapper', () => { }); }); }); - -describe('ConditionalPortal', () => { - const mount = useMountAppended(); - const props = { - usePortal: false, - registerProvider: jest.fn(), - isDragging: true, - }; - - it(`doesn't call registerProvider is NOT isDragging`, () => { - mount( - -
- - ); - - expect(props.registerProvider.mock.calls.length).toEqual(0); - }); - - it('calls registerProvider when isDragging', () => { - mount( - -
- - ); - - expect(props.registerProvider.mock.calls.length).toEqual(1); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx index 4b80b9fff2740e..7d84403b87f8d0 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect } from 'react'; import { Draggable, DraggableProvided, DraggableStateSnapshot, Droppable, } from 'react-beautiful-dnd'; -import { useDispatch } from 'react-redux'; +import { connect, ConnectedProps } from 'react-redux'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -47,50 +47,34 @@ const ProviderContentWrapper = styled.span` } `; -type RenderFunctionProp = ( - props: DataProvider, - provided: DraggableProvided, - state: DraggableStateSnapshot -) => React.ReactNode; - interface OwnProps { dataProvider: DataProvider; inline?: boolean; - render: RenderFunctionProp; + render: ( + props: DataProvider, + provided: DraggableProvided, + state: DraggableStateSnapshot + ) => React.ReactNode; truncate?: boolean; } -type Props = OwnProps; +type Props = OwnProps & PropsFromRedux; /** * Wraps a draggable component to handle registration / unregistration of the * data provider associated with the item being dropped */ -export const DraggableWrapper = React.memo( - ({ dataProvider, render, truncate }) => { - const [providerRegistered, setProviderRegistered] = useState(false); - const dispatch = useDispatch(); +const DraggableWrapperComponent = React.memo( + ({ dataProvider, registerProvider, render, truncate, unRegisterProvider }) => { const usePortal = useDraggablePortalContext(); - const registerProvider = useCallback(() => { - if (!providerRegistered) { - dispatch(dragAndDropActions.registerProvider({ provider: dataProvider })); - setProviderRegistered(true); - } - }, [dispatch, providerRegistered, dataProvider]); - - const unRegisterProvider = useCallback( - () => dispatch(dragAndDropActions.unRegisterProvider({ id: dataProvider.id })), - [dispatch, dataProvider] - ); - - useEffect( - () => () => { - unRegisterProvider(); - }, - [] - ); + useEffect(() => { + registerProvider!({ provider: dataProvider }); + return () => { + unRegisterProvider!({ id: dataProvider.id }); + }; + }, []); return ( @@ -103,18 +87,13 @@ export const DraggableWrapper = React.memo( key={getDraggableId(dataProvider.id)} > {(provided, snapshot) => ( - + ( ); }, - (prevProps, nextProps) => - deepEqual(prevProps.dataProvider, nextProps.dataProvider) && - prevProps.render !== nextProps.render && - prevProps.truncate === nextProps.truncate + (prevProps, nextProps) => { + return ( + deepEqual(prevProps.dataProvider, nextProps.dataProvider) && + prevProps.render !== nextProps.render && + prevProps.truncate === nextProps.truncate + ); + } ); +DraggableWrapperComponent.displayName = 'DraggableWrapperComponent'; + +const mapDispatchToProps = { + registerProvider: dragAndDropActions.registerProvider, + unRegisterProvider: dragAndDropActions.unRegisterProvider, +}; + +const connector = connect(null, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +export const DraggableWrapper = connector(DraggableWrapperComponent); + DraggableWrapper.displayName = 'DraggableWrapper'; /** @@ -155,24 +150,8 @@ DraggableWrapper.displayName = 'DraggableWrapper'; * * See: https://github.com/atlassian/react-beautiful-dnd/issues/499 */ - -interface ConditionalPortalProps { - children: React.ReactNode; - usePortal: boolean; - isDragging: boolean; - registerProvider: () => void; -} - -export const ConditionalPortal = React.memo( - ({ children, usePortal, registerProvider, isDragging }) => { - useEffect(() => { - if (isDragging) { - registerProvider(); - } - }, [isDragging, registerProvider]); - - return usePortal ? {children} : <>{children}; - } +const ConditionalPortal = React.memo<{ children: React.ReactNode; usePortal: boolean }>( + ({ children, usePortal }) => (usePortal ? {children} : <>{children}) ); ConditionalPortal.displayName = 'ConditionalPortal'; From facae44f5b17481cf450e5b310b33a16239feff0 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 27 Feb 2020 18:03:04 -0700 Subject: [PATCH 19/28] skip flaky suite (#53888) --- test/functional/apps/context/_size.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/context/_size.js b/test/functional/apps/context/_size.js index 5f3d1ebe409744..ea9b2c8cf18192 100644 --- a/test/functional/apps/context/_size.js +++ b/test/functional/apps/context/_size.js @@ -30,7 +30,8 @@ export default function({ getService, getPageObjects }) { const docTable = getService('docTable'); const PageObjects = getPageObjects(['context']); - describe('context size', function contextSize() { + // FLAKY: https://github.com/elastic/kibana/issues/53888 + describe.skip('context size', function contextSize() { before(async function() { await kibanaServer.uiSettings.update({ 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, From 8c3d71b370fadf5872185decedc34886ae151263 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 27 Feb 2020 21:15:08 -0500 Subject: [PATCH 20/28] [ML] NP: migrate server (#58680) * remove obsolete legacy server deps * licensePreRoutingFactory uses licensing plugin rather than legacy xpack * move schemas to dir in routes * use NP license check method for license check * store license data in plugin for passing to check * create server plugin files in NP plugin dir * remove dependency on legacy xpack plugin * add sample data links first step * move all server dirs from legacy to np dir * fix requiredPlugin spaces name and update import routes * delete unnecessary files and add sample data links * update license and privilege check tests * add routeInit types --- x-pack/.i18nrc.json | 2 +- .../legacy/plugins/ml/common/constants/app.ts | 2 +- .../plugins/ml/common/constants/license.ts | 2 + x-pack/legacy/plugins/ml/index.ts | 55 +--- x-pack/legacy/plugins/ml/kibana.json | 8 - .../application/license/check_license.tsx | 11 +- .../call_with_internal_user_factory.d.ts | 9 - .../client/call_with_internal_user_factory.js | 18 -- .../call_with_internal_user_factory.test.ts | 28 --- .../client/call_with_request_factory.js | 21 -- .../ml/server/lib/__tests__/security_utils.js | 35 --- .../plugins/ml/server/lib/security_utils.d.ts | 9 - .../plugins/ml/server/lib/security_utils.js | 19 -- .../plugins/ml/server/new_platform/plugin.ts | 238 ------------------ x-pack/plugins/ml/kibana.json | 9 + .../client/__tests__/elasticsearch_ml.js | 0 .../ml/server/client/elasticsearch_ml.js | 0 .../plugins/ml/server/client/error_wrapper.ts | 0 .../plugins/ml/server/client/errors.js | 0 .../plugins/ml/server/client/log.ts | 2 +- .../ml/server}/index.ts | 7 +- .../ml/server/lib/__tests__/query_utils.js | 0 .../server/lib/check_annotations/index.d.ts | 0 .../ml/server/lib/check_annotations/index.js | 2 +- .../lib/check_license/check_license.test.ts | 46 ++-- .../server/lib/check_license/check_license.ts | 22 +- .../ml/server/lib/check_license/index.ts | 0 .../__mocks__/call_with_request.ts | 0 .../check_privileges/check_privileges.test.ts | 120 +++------ .../lib/check_privileges/check_privileges.ts | 16 +- .../ml/server/lib/check_privileges/index.ts | 0 .../server/lib/check_privileges/privileges.ts | 0 .../ml/server/lib/check_privileges/upgrade.ts | 0 .../ml/server/lib/ml_telemetry/index.ts | 0 .../ml_telemetry/make_ml_usage_collector.ts | 0 .../lib/ml_telemetry/ml_telemetry.test.ts | 30 +-- .../server/lib/ml_telemetry/ml_telemetry.ts | 19 +- .../plugins/ml/server/lib/query_utils.ts | 0 .../ml/server/lib/sample_data_sets/index.ts | 0 .../lib/sample_data_sets/sample_data_sets.ts | 0 .../plugins/ml/server/lib/spaces_utils.ts | 7 +- .../__mocks__/get_annotations_request.json | 0 .../__mocks__/get_annotations_response.json | 0 .../annotation_service/annotation.test.ts | 9 +- .../models/annotation_service/annotation.ts | 6 +- .../server/models/annotation_service/index.ts | 0 .../__tests__/bucket_span_estimator.js | 0 .../bucket_span_estimator.d.ts | 4 +- .../bucket_span_estimator.js | 10 +- .../models/bucket_span_estimator/index.ts | 0 .../models/bucket_span_estimator/intervals.js | 0 .../polled_data_checker.js | 0 .../single_series_checker.js | 0 .../calculate_model_memory_limit.d.ts | 0 .../calculate_model_memory_limit.js | 0 .../calculate_model_memory_limit/index.ts | 0 .../models/calendar/calendar_manager.ts | 0 .../server/models/calendar/event_manager.ts | 2 +- .../ml/server/models/calendar/index.ts | 0 .../analytics_audit_messages.ts | 6 +- .../models/data_frame_analytics/index.js | 0 .../data_recognizer/data_recognizer.test.ts | 2 +- .../models/data_recognizer/data_recognizer.ts | 9 +- .../ml/server/models/data_recognizer/index.ts | 0 .../ml_http_access_explorer_ecs.json | 0 .../search/ml_http_access_filebeat_ecs.json | 0 .../ml_http_access_events_timechart_ecs.json | 0 .../visualization/ml_http_access_map_ecs.json | 0 ...l_http_access_source_ip_timechart_ecs.json | 0 ...http_access_status_code_timechart_ecs.json | 0 ..._http_access_top_source_ips_table_ecs.json | 0 .../ml_http_access_top_urls_table_ecs.json | 0 ...access_unique_count_url_timechart_ecs.json | 0 .../modules/apache_ecs/logo.json | 0 .../modules/apache_ecs/manifest.json | 0 .../ml/datafeed_low_request_rate_ecs.json | 0 .../datafeed_source_ip_request_rate_ecs.json | 0 .../ml/datafeed_source_ip_url_count_ecs.json | 0 .../ml/datafeed_status_code_rate_ecs.json | 0 .../ml/datafeed_visitor_rate_ecs.json | 0 .../apache_ecs/ml/low_request_rate_ecs.json | 0 .../ml/source_ip_request_rate_ecs.json | 0 .../ml/source_ip_url_count_ecs.json | 0 .../apache_ecs/ml/status_code_rate_ecs.json | 0 .../apache_ecs/ml/visitor_rate_ecs.json | 0 .../modules/apm_jsbase/logo.json | 0 .../modules/apm_jsbase/manifest.json | 0 .../ml/abnormal_span_durations_jsbase.json | 0 ...ous_error_rate_for_user_agents_jsbase.json | 0 ...tafeed_abnormal_span_durations_jsbase.json | 0 ...ous_error_rate_for_user_agents_jsbase.json | 0 .../datafeed_decreased_throughput_jsbase.json | 0 ...afeed_high_count_by_user_agent_jsbase.json | 0 .../ml/decreased_throughput_jsbase.json | 0 .../ml/high_count_by_user_agent_jsbase.json | 0 .../modules/apm_nodejs/logo.json | 0 .../modules/apm_nodejs/manifest.json | 0 .../ml/abnormal_span_durations_nodejs.json | 0 .../ml/abnormal_trace_durations_nodejs.json | 0 ...tafeed_abnormal_span_durations_nodejs.json | 0 ...afeed_abnormal_trace_durations_nodejs.json | 0 .../datafeed_decreased_throughput_nodejs.json | 0 .../ml/decreased_throughput_nodejs.json | 0 .../modules/apm_transaction/logo.json | 0 .../modules/apm_transaction/manifest.json | 0 .../ml/datafeed_high_mean_response_time.json | 0 .../ml/high_mean_response_time.json | 0 ...ditbeat_docker_process_event_rate_ecs.json | 0 ...auditbeat_docker_process_explorer_ecs.json | 0 ...l_auditbeat_docker_process_events_ecs.json | 0 ...ker_process_event_rate_by_process_ecs.json | 0 ...eat_docker_process_event_rate_vis_ecs.json | 0 ...ditbeat_docker_process_occurrence_ecs.json | 0 .../auditbeat_process_docker_ecs/logo.json | 0 .../manifest.json | 0 ..._docker_high_count_process_events_ecs.json | 0 ...feed_docker_rare_process_activity_ecs.json | 0 .../docker_high_count_process_events_ecs.json | 0 .../ml/docker_rare_process_activity_ecs.json | 0 ...uditbeat_hosts_process_event_rate_ecs.json | 0 ..._auditbeat_hosts_process_explorer_ecs.json | 0 ...ml_auditbeat_hosts_process_events_ecs.json | 0 ...sts_process_event_rate_by_process_ecs.json | 0 ...beat_hosts_process_event_rate_vis_ecs.json | 0 ...uditbeat_hosts_process_occurrence_ecs.json | 0 .../auditbeat_process_hosts_ecs/logo.json | 0 .../auditbeat_process_hosts_ecs/manifest.json | 0 ...d_hosts_high_count_process_events_ecs.json | 0 ...afeed_hosts_rare_process_activity_ecs.json | 0 .../hosts_high_count_process_events_ecs.json | 0 .../ml/hosts_rare_process_activity_ecs.json | 0 .../modules/logs_ui_analysis/logo.json | 0 .../modules/logs_ui_analysis/manifest.json | 0 .../ml/datafeed_log_entry_rate.json | 0 .../logs_ui_analysis/ml/log_entry_rate.json | 0 .../modules/logs_ui_categories/logo.json | 0 .../modules/logs_ui_categories/manifest.json | 0 .../datafeed_log_entry_categories_count.json | 0 .../ml/log_entry_categories_count.json | 0 .../modules/metricbeat_system_ecs/logo.json | 0 .../metricbeat_system_ecs/manifest.json | 0 .../ml/datafeed_high_mean_cpu_iowait_ecs.json | 0 .../ml/datafeed_max_disk_utilization_ecs.json | 0 .../ml/datafeed_metricbeat_outages_ecs.json | 0 .../ml/high_mean_cpu_iowait_ecs.json | 0 .../ml/max_disk_utilization_ecs.json | 0 .../ml/metricbeat_outages_ecs.json | 0 .../ml_http_access_explorer_ecs.json | 0 .../search/ml_http_access_filebeat_ecs.json | 0 .../ml_http_access_events_timechart_ecs.json | 0 .../visualization/ml_http_access_map_ecs.json | 0 ...l_http_access_source_ip_timechart_ecs.json | 0 ...http_access_status_code_timechart_ecs.json | 0 ..._http_access_top_source_ips_table_ecs.json | 0 .../ml_http_access_top_urls_table_ecs.json | 0 ...access_unique_count_url_timechart_ecs.json | 0 .../modules/nginx_ecs/logo.json | 0 .../modules/nginx_ecs/manifest.json | 0 .../ml/datafeed_low_request_rate_ecs.json | 0 .../datafeed_source_ip_request_rate_ecs.json | 0 .../ml/datafeed_source_ip_url_count_ecs.json | 0 .../ml/datafeed_status_code_rate_ecs.json | 0 .../ml/datafeed_visitor_rate_ecs.json | 0 .../nginx_ecs/ml/low_request_rate_ecs.json | 0 .../ml/source_ip_request_rate_ecs.json | 0 .../nginx_ecs/ml/source_ip_url_count_ecs.json | 0 .../nginx_ecs/ml/status_code_rate_ecs.json | 0 .../nginx_ecs/ml/visitor_rate_ecs.json | 0 .../modules/sample_data_ecommerce/logo.json | 0 .../sample_data_ecommerce/manifest.json | 0 .../ml/datafeed_high_sum_total_sales.json | 0 .../ml/high_sum_total_sales.json | 0 .../modules/sample_data_weblogs/logo.json | 0 .../modules/sample_data_weblogs/manifest.json | 0 .../ml/datafeed_low_request_rate.json | 0 .../ml/datafeed_response_code_rates.json | 0 .../ml/datafeed_url_scanning.json | 0 .../ml/low_request_rate.json | 0 .../ml/response_code_rates.json | 0 .../sample_data_weblogs/ml/url_scanning.json | 0 .../modules/siem_auditbeat/logo.json | 0 .../modules/siem_auditbeat/manifest.json | 0 ..._linux_anomalous_network_activity_ecs.json | 0 ...x_anomalous_network_port_activity_ecs.json | 0 ...afeed_linux_anomalous_network_service.json | 0 ...ux_anomalous_network_url_activity_ecs.json | 0 ...linux_anomalous_process_all_hosts_ecs.json | 0 ...atafeed_linux_anomalous_user_name_ecs.json | 0 ...tafeed_rare_process_by_host_linux_ecs.json | 0 .../linux_anomalous_network_activity_ecs.json | 0 ...x_anomalous_network_port_activity_ecs.json | 0 .../ml/linux_anomalous_network_service.json | 0 ...ux_anomalous_network_url_activity_ecs.json | 0 ...linux_anomalous_process_all_hosts_ecs.json | 0 .../ml/linux_anomalous_user_name_ecs.json | 0 .../ml/rare_process_by_host_linux_ecs.json | 0 .../modules/siem_auditbeat_auth/logo.json | 0 .../modules/siem_auditbeat_auth/manifest.json | 0 ...atafeed_suspicious_login_activity_ecs.json | 0 .../ml/suspicious_login_activity_ecs.json | 0 .../modules/siem_packetbeat/logo.json | 0 .../modules/siem_packetbeat/manifest.json | 0 .../ml/datafeed_packetbeat_dns_tunneling.json | 0 ...datafeed_packetbeat_rare_dns_question.json | 0 ...atafeed_packetbeat_rare_server_domain.json | 0 .../ml/datafeed_packetbeat_rare_urls.json | 0 .../datafeed_packetbeat_rare_user_agent.json | 0 .../ml/packetbeat_dns_tunneling.json | 0 .../ml/packetbeat_rare_dns_question.json | 0 .../ml/packetbeat_rare_server_domain.json | 0 .../ml/packetbeat_rare_urls.json | 0 .../ml/packetbeat_rare_user_agent.json | 0 .../modules/siem_winlogbeat/logo.json | 0 .../modules/siem_winlogbeat/manifest.json | 0 ...feed_rare_process_by_host_windows_ecs.json | 0 ...indows_anomalous_network_activity_ecs.json | 0 ...d_windows_anomalous_path_activity_ecs.json | 0 ...ndows_anomalous_process_all_hosts_ecs.json | 0 ...ed_windows_anomalous_process_creation.json | 0 .../ml/datafeed_windows_anomalous_script.json | 0 .../datafeed_windows_anomalous_service.json | 0 ...afeed_windows_anomalous_user_name_ecs.json | 0 ...atafeed_windows_rare_user_runas_event.json | 0 .../ml/rare_process_by_host_windows_ecs.json | 0 ...indows_anomalous_network_activity_ecs.json | 0 .../windows_anomalous_path_activity_ecs.json | 0 ...ndows_anomalous_process_all_hosts_ecs.json | 0 .../windows_anomalous_process_creation.json | 0 .../ml/windows_anomalous_script.json | 0 .../ml/windows_anomalous_service.json | 0 .../ml/windows_anomalous_user_name_ecs.json | 0 .../ml/windows_rare_user_runas_event.json | 0 .../modules/siem_winlogbeat_auth/logo.json | 0 .../siem_winlogbeat_auth/manifest.json | 0 ...windows_rare_user_type10_remote_login.json | 0 ...windows_rare_user_type10_remote_login.json | 0 .../models/data_visualizer/data_visualizer.ts | 4 +- .../ml/server/models/data_visualizer/index.ts | 0 .../models/fields_service/fields_service.d.ts | 0 .../models/fields_service/fields_service.js | 0 .../ml/server/models/fields_service/index.ts | 0 .../file_data_visualizer.ts | 2 +- .../file_data_visualizer/import_data.ts | 2 +- .../models/file_data_visualizer/index.ts | 0 .../ml/server/models/filter/filter_manager.ts | 5 +- .../plugins/ml/server/models/filter/index.js | 0 .../plugins/ml/server/models/filter/index.ts | 0 .../server/models/job_audit_messages/index.ts | 0 .../job_audit_messages.d.ts | 0 .../job_audit_messages/job_audit_messages.js | 2 +- .../ml/server/models/job_service/datafeeds.js | 5 +- .../server/models/job_service/error_utils.js | 5 +- .../ml/server/models/job_service/groups.js | 2 +- .../ml/server/models/job_service/index.js | 0 .../ml/server/models/job_service/jobs.js | 7 +- .../new_job/categorization/examples.ts | 6 +- .../new_job/categorization/index.ts | 0 .../new_job/categorization/top_categories.ts | 9 +- .../categorization/validation_results.ts | 6 +- .../models/job_service/new_job/charts.ts | 2 +- .../models/job_service/new_job/index.ts | 0 .../models/job_service/new_job/line_chart.ts | 9 +- .../job_service/new_job/population_chart.ts | 9 +- .../responses/cloudwatch_field_caps.json | 0 .../responses/farequote_field_caps.json | 0 .../responses/kibana_saved_objects.json | 0 .../__mocks__/responses/rollup_caps.json | 0 .../results/cloudwatch_rollup_job_caps.json | 0 .../__mocks__/results/farequote_job_caps.json | 0 .../results/farequote_job_caps_empty.json | 0 .../job_service/new_job_caps/aggregations.ts | 7 +- .../job_service/new_job_caps/field_service.ts | 6 +- .../models/job_service/new_job_caps/index.ts | 0 .../new_job_caps/new_job_caps.test.ts | 0 .../job_service/new_job_caps/new_job_caps.ts | 6 +- .../models/job_service/new_job_caps/rollup.ts | 4 +- .../__tests__/job_validation.js | 0 .../__tests__/mock_farequote_cardinality.json | 0 .../mock_farequote_search_response.json | 0 .../__tests__/mock_field_caps.json | 0 .../__tests__/mock_it_search_response.json | 0 .../__tests__/mock_time_field.json | 0 .../__tests__/mock_time_field_nested.json | 0 .../__tests__/mock_time_range.json | 0 .../__tests__/validate_bucket_span.js | 2 +- .../__tests__/validate_cardinality.js | 0 .../__tests__/validate_influencers.js | 0 .../__tests__/validate_model_memory_limit.js | 0 .../__tests__/validate_time_range.js | 0 .../ml/server/models/job_validation/index.ts | 0 .../models/job_validation/job_validation.d.ts | 4 +- .../models/job_validation/job_validation.js | 13 +- .../server/models/job_validation/messages.js | 2 +- .../job_validation/validate_bucket_span.js | 10 +- .../job_validation/validate_cardinality.d.ts | 5 +- .../job_validation/validate_cardinality.js | 0 .../job_validation/validate_influencers.js | 0 .../job_validation/validate_job_object.js | 0 .../validate_model_memory_limit.js | 2 +- .../job_validation/validate_time_range.js | 4 +- .../build_anomaly_table_items.d.ts | 2 +- .../build_anomaly_table_items.js | 2 +- .../get_partition_fields_values.ts | 4 +- .../ml/server/models/results_service/index.ts | 0 .../models/results_service/results_service.ts | 6 +- x-pack/plugins/ml/server/plugin.ts | 168 +++++++++++++ .../plugins/ml/server/routes/README.md | 0 .../plugins/ml/server/routes/annotations.ts | 21 +- .../ml/server/routes/anomaly_detectors.ts | 38 +-- .../plugins/ml/server/routes/apidoc.json | 0 .../plugins/ml/server/routes/calendars.ts | 18 +- .../ml/server/routes/data_frame_analytics.ts | 30 +-- .../ml/server/routes/data_visualizer.ts | 12 +- .../plugins/ml/server/routes/datafeeds.ts | 28 +-- .../ml/server/routes/fields_service.ts | 12 +- .../ml/server/routes/file_data_visualizer.ts | 20 +- .../plugins/ml/server/routes/filters.ts | 20 +- .../plugins/ml/server/routes/indices.ts | 8 +- .../ml/server/routes/job_audit_messages.ts | 10 +- .../plugins/ml/server/routes/job_service.ts | 48 ++-- .../ml/server/routes/job_validation.ts | 26 +- .../license_check_pre_routing_factory.ts} | 12 +- .../plugins/ml/server/routes/modules.ts | 18 +- .../ml/server/routes/notification_settings.ts | 8 +- .../ml/server/routes/results_service.ts | 18 +- .../routes/schemas}/annotations_schema.ts | 0 .../schemas}/anomaly_detectors_schema.ts | 0 .../routes/schemas}/calendars_schema.ts | 0 .../routes/schemas}/data_analytics_schema.ts | 0 .../routes/schemas}/data_visualizer_schema.ts | 0 .../routes/schemas}/datafeeds_schema.ts | 0 .../routes/schemas}/fields_service_schema.ts | 0 .../server/routes/schemas}/filters_schema.ts | 0 .../routes/schemas}/job_service_schema.ts | 0 .../routes/schemas}/job_validation_schema.ts | 0 .../ml/server/routes/schemas}/modules.ts | 0 .../routes/schemas}/results_service_schema.ts | 0 .../plugins/ml/server/routes/system.ts | 31 ++- x-pack/plugins/ml/server/types.ts | 43 ++++ 339 files changed, 655 insertions(+), 873 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/kibana.json delete mode 100644 x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts delete mode 100644 x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js delete mode 100644 x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts delete mode 100644 x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js delete mode 100644 x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js delete mode 100644 x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts delete mode 100644 x-pack/legacy/plugins/ml/server/lib/security_utils.js delete mode 100644 x-pack/legacy/plugins/ml/server/new_platform/plugin.ts create mode 100644 x-pack/plugins/ml/kibana.json rename x-pack/{legacy => }/plugins/ml/server/client/__tests__/elasticsearch_ml.js (100%) rename x-pack/{legacy => }/plugins/ml/server/client/elasticsearch_ml.js (100%) rename x-pack/{legacy => }/plugins/ml/server/client/error_wrapper.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/client/errors.js (100%) rename x-pack/{legacy => }/plugins/ml/server/client/log.ts (94%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server}/index.ts (57%) rename x-pack/{legacy => }/plugins/ml/server/lib/__tests__/query_utils.js (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_annotations/index.d.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_annotations/index.js (95%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_license/check_license.test.ts (81%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_license/check_license.ts (75%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_license/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_privileges/check_privileges.test.ts (91%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_privileges/check_privileges.ts (94%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_privileges/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_privileges/privileges.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/check_privileges/upgrade.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/ml_telemetry/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts (85%) rename x-pack/{legacy => }/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts (74%) rename x-pack/{legacy => }/plugins/ml/server/lib/query_utils.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/sample_data_sets/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/lib/spaces_utils.ts (75%) rename x-pack/{legacy => }/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/annotation_service/annotation.test.ts (96%) rename x-pack/{legacy => }/plugins/ml/server/models/annotation_service/annotation.ts (96%) rename x-pack/{legacy => }/plugins/ml/server/models/annotation_service/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts (75%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js (98%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/intervals.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/calculate_model_memory_limit/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/calendar/calendar_manager.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/calendar/event_manager.ts (94%) rename x-pack/{legacy => }/plugins/ml/server/models/calendar/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts (88%) rename x-pack/{legacy => }/plugins/ml/server/models/data_frame_analytics/index.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts (97%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/data_recognizer.ts (99%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/data_visualizer/data_visualizer.ts (99%) rename x-pack/{legacy => }/plugins/ml/server/models/data_visualizer/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/fields_service/fields_service.d.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/fields_service/fields_service.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/fields_service/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts (95%) rename x-pack/{legacy => }/plugins/ml/server/models/file_data_visualizer/import_data.ts (97%) rename x-pack/{legacy => }/plugins/ml/server/models/file_data_visualizer/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/filter/filter_manager.ts (98%) rename x-pack/{legacy => }/plugins/ml/server/models/filter/index.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/filter/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_audit_messages/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_audit_messages/job_audit_messages.js (98%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/datafeeds.js (97%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/error_utils.js (94%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/groups.js (95%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/index.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/jobs.js (98%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/categorization/examples.ts (95%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/categorization/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts (92%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts (96%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/charts.ts (87%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/line_chart.ts (92%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job/population_chart.ts (95%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts (97%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/field_service.ts (96%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts (93%) rename x-pack/{legacy => }/plugins/ml/server/models/job_service/new_job_caps/rollup.ts (92%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/job_validation.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js (98%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/job_validation.d.ts (83%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/job_validation.js (94%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/messages.js (99%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_bucket_span.js (93%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_cardinality.d.ts (78%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_cardinality.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_influencers.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_job_object.js (100%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_model_memory_limit.js (98%) rename x-pack/{legacy => }/plugins/ml/server/models/job_validation/validate_time_range.js (93%) rename x-pack/{legacy => }/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts (89%) rename x-pack/{legacy => }/plugins/ml/server/models/results_service/build_anomaly_table_items.js (99%) rename x-pack/{legacy => }/plugins/ml/server/models/results_service/get_partition_fields_values.ts (95%) rename x-pack/{legacy => }/plugins/ml/server/models/results_service/index.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/models/results_service/results_service.ts (97%) create mode 100644 x-pack/plugins/ml/server/plugin.ts rename x-pack/{legacy => }/plugins/ml/server/routes/README.md (100%) rename x-pack/{legacy => }/plugins/ml/server/routes/annotations.ts (83%) rename x-pack/{legacy => }/plugins/ml/server/routes/anomaly_detectors.ts (89%) rename x-pack/{legacy => }/plugins/ml/server/routes/apidoc.json (100%) rename x-pack/{legacy => }/plugins/ml/server/routes/calendars.ts (84%) rename x-pack/{legacy => }/plugins/ml/server/routes/data_frame_analytics.ts (89%) rename x-pack/{legacy => }/plugins/ml/server/routes/data_visualizer.ts (89%) rename x-pack/{legacy => }/plugins/ml/server/routes/datafeeds.ts (86%) rename x-pack/{legacy => }/plugins/ml/server/routes/fields_service.ts (84%) rename x-pack/{legacy => }/plugins/ml/server/routes/file_data_visualizer.ts (87%) rename x-pack/{legacy => }/plugins/ml/server/routes/filters.ts (87%) rename x-pack/{legacy => }/plugins/ml/server/routes/indices.ts (80%) rename x-pack/{legacy => }/plugins/ml/server/routes/job_audit_messages.ts (84%) rename x-pack/{legacy => }/plugins/ml/server/routes/job_service.ts (88%) rename x-pack/{legacy => }/plugins/ml/server/routes/job_validation.ts (85%) rename x-pack/{legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts => plugins/ml/server/routes/license_check_pre_routing_factory.ts} (71%) rename x-pack/{legacy => }/plugins/ml/server/routes/modules.ts (88%) rename x-pack/{legacy => }/plugins/ml/server/routes/notification_settings.ts (75%) rename x-pack/{legacy => }/plugins/ml/server/routes/results_service.ts (88%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/annotations_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/anomaly_detectors_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/calendars_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/data_analytics_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/data_visualizer_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/datafeeds_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/fields_service_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/filters_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/job_service_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/job_validation_schema.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/modules.ts (100%) rename x-pack/{legacy/plugins/ml/server/new_platform => plugins/ml/server/routes/schemas}/results_service_schema.ts (100%) rename x-pack/{legacy => }/plugins/ml/server/routes/system.ts (88%) create mode 100644 x-pack/plugins/ml/server/types.ts diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index bb084b3bb72a18..66342266f1dbc6 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -26,7 +26,7 @@ "xpack.logstash": "legacy/plugins/logstash", "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": "legacy/plugins/maps", - "xpack.ml": "legacy/plugins/ml", + "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "plugins/remote_clusters", "xpack.reporting": ["plugins/reporting", "legacy/plugins/reporting"], diff --git a/x-pack/legacy/plugins/ml/common/constants/app.ts b/x-pack/legacy/plugins/ml/common/constants/app.ts index 140a709b0c42be..bbec35a17faa54 100644 --- a/x-pack/legacy/plugins/ml/common/constants/app.ts +++ b/x-pack/legacy/plugins/ml/common/constants/app.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const API_BASE_PATH = '/api/transform/'; +export const PLUGIN_ID = 'ml'; diff --git a/x-pack/legacy/plugins/ml/common/constants/license.ts b/x-pack/legacy/plugins/ml/common/constants/license.ts index 2027e2c8b18653..183844c9ef980c 100644 --- a/x-pack/legacy/plugins/ml/common/constants/license.ts +++ b/x-pack/legacy/plugins/ml/common/constants/license.ts @@ -8,3 +8,5 @@ export enum LICENSE_TYPE { BASIC, FULL, // >= platinum } + +export const VALID_FULL_LICENSE_MODES = ['platinum', 'enterprise', 'trial']; diff --git a/x-pack/legacy/plugins/ml/index.ts b/x-pack/legacy/plugins/ml/index.ts index 09f1b9ccedce4f..47df7c8c3e5e62 100755 --- a/x-pack/legacy/plugins/ml/index.ts +++ b/x-pack/legacy/plugins/ml/index.ts @@ -6,23 +6,13 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; -import KbnServer, { Server } from 'src/legacy/server/kbn_server'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { plugin } from './server/new_platform'; -import { CloudSetup } from '../../../plugins/cloud/server'; +import { Server } from 'src/legacy/server/kbn_server'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; -import { - MlInitializerContext, - MlCoreSetup, - MlHttpServiceSetup, -} from './server/new_platform/plugin'; +// @ts-ignore: could not find declaration file for module +import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; // @ts-ignore: could not find declaration file for module import mappings from './mappings'; -interface MlServer extends Server { - addAppLinksToSampleDataset: () => {}; -} - export const ml = (kibana: any) => { return new kibana.Plugin({ require: ['kibana', 'elasticsearch', 'xpack_main'], @@ -60,43 +50,8 @@ export const ml = (kibana: any) => { }, }, - async init(server: MlServer) { - const kbnServer = (server as unknown) as KbnServer; - - const initializerContext = ({ - legacyConfig: server.config(), - logger: { - get(...contextParts: string[]) { - return kbnServer.newPlatform.coreContext.logger.get('plugins', 'ml', ...contextParts); - }, - }, - } as unknown) as MlInitializerContext; - - const mlHttpService: MlHttpServiceSetup = { - ...kbnServer.newPlatform.setup.core.http, - route: server.route.bind(server), - }; - - const core: MlCoreSetup = { - injectUiAppVars: server.injectUiAppVars, - http: mlHttpService, - savedObjects: server.savedObjects, - coreSavedObjects: kbnServer.newPlatform.start.core.savedObjects, - elasticsearch: kbnServer.newPlatform.setup.core.elasticsearch, - }; - const { usageCollection, cloud, home } = kbnServer.newPlatform.setup.plugins; - const plugins = { - elasticsearch: server.plugins.elasticsearch, // legacy - security: server.newPlatform.setup.plugins.security, - xpackMain: server.plugins.xpack_main, - spaces: server.plugins.spaces, - home, - usageCollection: usageCollection as UsageCollectionSetup, - cloud: cloud as CloudSetup, - ml: this, - }; - - plugin(initializerContext).setup(core, plugins); + async init(server: Server) { + mirrorPluginStatus(server.plugins.xpack_main, this); }, }); }; diff --git a/x-pack/legacy/plugins/ml/kibana.json b/x-pack/legacy/plugins/ml/kibana.json deleted file mode 100644 index f36b4848186906..00000000000000 --- a/x-pack/legacy/plugins/ml/kibana.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "ml", - "version": "0.0.1", - "kibanaVersion": "kibana", - "configPath": ["ml"], - "server": true, - "ui": true -} diff --git a/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx b/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx index 96e6aab3779621..4af753ddb4d1f2 100644 --- a/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx +++ b/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx @@ -82,9 +82,16 @@ function setLicenseExpired(features: any) { } } } - +// Temporary hack for cutting over server to NP function getFeatures() { - return xpackInfo.get('features.ml'); + return { + isAvailable: true, + showLinks: true, + enableLinks: true, + licenseType: 1, + hasExpired: false, + }; + // return xpackInfo.get('features.ml'); } function redirectToKibana() { diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts b/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts deleted file mode 100644 index bf2e656afff123..00000000000000 --- a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts +++ /dev/null @@ -1,9 +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 { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; - -export function callWithInternalUserFactory(elasticsearchPlugin: ElasticsearchPlugin): any; diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js b/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js deleted file mode 100644 index 2e5431bdd6ce2d..00000000000000 --- a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const _callWithInternalUser = once(elasticsearchPlugin => { - const { callWithInternalUser } = elasticsearchPlugin.getCluster('admin'); - return callWithInternalUser; -}); - -export const callWithInternalUserFactory = elasticsearchPlugin => { - return (...args) => { - return _callWithInternalUser(elasticsearchPlugin)(...args); - }; -}; diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts b/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts deleted file mode 100644 index be016cc13ed0f3..00000000000000 --- a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts +++ /dev/null @@ -1,28 +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 { callWithInternalUserFactory } from './call_with_internal_user_factory'; - -describe('call_with_internal_user_factory', () => { - describe('callWithInternalUserFactory', () => { - let elasticsearchPlugin: any; - let callWithInternalUser: any; - - beforeEach(() => { - callWithInternalUser = jest.fn(); - elasticsearchPlugin = { - getCluster: jest.fn(() => ({ callWithInternalUser })), - }; - }); - - it('should use internal user "admin"', () => { - const callWithInternalUserInstance = callWithInternalUserFactory(elasticsearchPlugin); - callWithInternalUserInstance(); - - expect(elasticsearchPlugin.getCluster).toHaveBeenCalledWith('admin'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js b/x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js deleted file mode 100644 index b39a58b317500f..00000000000000 --- a/x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { elasticsearchJsPlugin } from './elasticsearch_ml'; - -const callWithRequest = once(elasticsearchPlugin => { - const config = { plugins: [elasticsearchJsPlugin] }; - const cluster = elasticsearchPlugin.createCluster('ml', config); - - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (elasticsearchPlugin, request) => { - return (...args) => { - return callWithRequest(elasticsearchPlugin)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js b/x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js deleted file mode 100644 index 6e0181f49072e1..00000000000000 --- a/x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js +++ /dev/null @@ -1,35 +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 expect from '@kbn/expect'; -import { isSecurityDisabled } from '../security_utils'; - -describe('ML - security utils', () => { - function mockXpackMainPluginFactory(isAvailable = true, isEnabled = true) { - return { - info: { - isAvailable: () => isAvailable, - feature: () => ({ - isEnabled: () => isEnabled, - }), - }, - }; - } - - describe('isSecurityDisabled', () => { - it('returns not disabled for given mock server object #1', () => { - expect(isSecurityDisabled(mockXpackMainPluginFactory())).to.be(false); - }); - - it('returns not disabled for given mock server object #2', () => { - expect(isSecurityDisabled(mockXpackMainPluginFactory(false))).to.be(false); - }); - - it('returns disabled for given mock server object #3', () => { - expect(isSecurityDisabled(mockXpackMainPluginFactory(true, false))).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts b/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts deleted file mode 100644 index 26fdff73b34606..00000000000000 --- a/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts +++ /dev/null @@ -1,9 +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 { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; - -export function isSecurityDisabled(xpackMainPlugin: XPackMainPlugin): boolean; diff --git a/x-pack/legacy/plugins/ml/server/lib/security_utils.js b/x-pack/legacy/plugins/ml/server/lib/security_utils.js deleted file mode 100644 index 27109e645b185b..00000000000000 --- a/x-pack/legacy/plugins/ml/server/lib/security_utils.js +++ /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. - */ - -/* - * Contains utility functions related to x-pack security. - */ - -export function isSecurityDisabled(xpackMainPlugin) { - const xpackInfo = xpackMainPlugin && xpackMainPlugin.info; - // we assume that `xpack.isAvailable()` always returns `true` because we're inside x-pack - // if for whatever reason it returns `false`, `isSecurityDisabled()` would also return `false` - // which would result in follow-up behavior assuming security is enabled. This is intentional, - // because it results in more defensive behavior. - const securityInfo = xpackInfo && xpackInfo.isAvailable() && xpackInfo.feature('security'); - return securityInfo && securityInfo.isEnabled() === false; -} diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts deleted file mode 100644 index 43c276ac63a13d..00000000000000 --- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts +++ /dev/null @@ -1,238 +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 Boom from 'boom'; -import { i18n } from '@kbn/i18n'; -import { ServerRoute } from 'hapi'; -import { KibanaConfig, SavedObjectsLegacyService } from 'src/legacy/server/kbn_server'; -import { - Logger, - PluginInitializerContext, - CoreSetup, - IRouter, - IScopedClusterClient, - SavedObjectsServiceStart, -} from 'src/core/server'; -import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { ElasticsearchServiceSetup } from 'src/core/server'; -import { CloudSetup } from '../../../../../plugins/cloud/server'; -import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; -import { addLinksToSampleDatasets } from '../lib/sample_data_sets'; -import { checkLicense } from '../lib/check_license'; -// @ts-ignore: could not find declaration file for module -import { mirrorPluginStatus } from '../../../../server/lib/mirror_plugin_status'; -import { LICENSE_TYPE } from '../../common/constants/license'; -import { annotationRoutes } from '../routes/annotations'; -import { jobRoutes } from '../routes/anomaly_detectors'; -import { dataFeedRoutes } from '../routes/datafeeds'; -import { indicesRoutes } from '../routes/indices'; -import { jobValidationRoutes } from '../routes/job_validation'; -import { makeMlUsageCollector } from '../lib/ml_telemetry'; -import { notificationRoutes } from '../routes/notification_settings'; -import { systemRoutes } from '../routes/system'; -import { dataFrameAnalyticsRoutes } from '../routes/data_frame_analytics'; -import { dataRecognizer } from '../routes/modules'; -import { dataVisualizerRoutes } from '../routes/data_visualizer'; -import { calendars } from '../routes/calendars'; -// @ts-ignore: could not find declaration file for module -import { fieldsService } from '../routes/fields_service'; -import { filtersRoutes } from '../routes/filters'; -import { resultsServiceRoutes } from '../routes/results_service'; -import { jobServiceRoutes } from '../routes/job_service'; -import { jobAuditMessagesRoutes } from '../routes/job_audit_messages'; -import { fileDataVisualizerRoutes } from '../routes/file_data_visualizer'; -import { initMlServerLog, LogInitialization } from '../client/log'; -import { HomeServerPluginSetup } from '../../../../../../src/plugins/home/server'; -// @ts-ignore: could not find declaration file for module -import { elasticsearchJsPlugin } from '../client/elasticsearch_ml'; - -export const PLUGIN_ID = 'ml'; - -type CoreHttpSetup = CoreSetup['http']; -export interface MlHttpServiceSetup extends CoreHttpSetup { - route(route: ServerRoute | ServerRoute[]): void; -} - -export interface MlXpackMainPlugin extends XPackMainPlugin { - status?: any; -} - -export interface MlCoreSetup { - injectUiAppVars: (id: string, callback: () => {}) => any; - http: MlHttpServiceSetup; - savedObjects: SavedObjectsLegacyService; - coreSavedObjects: SavedObjectsServiceStart; - elasticsearch: ElasticsearchServiceSetup; -} -export interface MlInitializerContext extends PluginInitializerContext { - legacyConfig: KibanaConfig; - log: Logger; -} -export interface PluginsSetup { - elasticsearch: ElasticsearchPlugin; - xpackMain: MlXpackMainPlugin; - security: any; - spaces: any; - usageCollection?: UsageCollectionSetup; - cloud?: CloudSetup; - home?: HomeServerPluginSetup; - // TODO: this is temporary for `mirrorPluginStatus` - ml: any; -} - -export interface RouteInitialization { - commonRouteConfig: any; - config?: any; - elasticsearchPlugin: ElasticsearchPlugin; - elasticsearchService: ElasticsearchServiceSetup; - route(route: ServerRoute | ServerRoute[]): void; - router: IRouter; - xpackMainPlugin: MlXpackMainPlugin; - savedObjects?: SavedObjectsServiceStart; - spacesPlugin: any; - securityPlugin: any; - cloud?: CloudSetup; -} - -declare module 'kibana/server' { - interface RequestHandlerContext { - ml?: { - mlClient: IScopedClusterClient; - }; - } -} - -export class Plugin { - private readonly pluginId: string = PLUGIN_ID; - private config: any; - private log: Logger; - - constructor(initializerContext: MlInitializerContext) { - this.config = initializerContext.legacyConfig; - this.log = initializerContext.logger.get(); - } - - public setup(core: MlCoreSetup, plugins: PluginsSetup) { - const xpackMainPlugin: MlXpackMainPlugin = plugins.xpackMain; - const { http, coreSavedObjects } = core; - const pluginId = this.pluginId; - - mirrorPluginStatus(xpackMainPlugin, plugins.ml); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - const mlFeature = xpackMainPlugin.info.feature(pluginId); - mlFeature.registerLicenseCheckResultsGenerator(checkLicense); - - // Add links to the Kibana sample data sets if ml is enabled - // and there is a full license (trial or platinum). - if (mlFeature.isEnabled() === true && plugins.home) { - const licenseCheckResults = mlFeature.getLicenseCheckResults(); - if (licenseCheckResults.licenseType === LICENSE_TYPE.FULL) { - addLinksToSampleDatasets({ - addAppLinksToSampleDataset: plugins.home.sampleData.addAppLinksToSampleDataset, - }); - } - } - }); - - xpackMainPlugin.registerFeature({ - id: 'ml', - name: i18n.translate('xpack.ml.featureRegistry.mlFeatureName', { - defaultMessage: 'Machine Learning', - }), - icon: 'machineLearningApp', - navLinkId: 'ml', - app: ['ml', 'kibana'], - catalogue: ['ml'], - privileges: {}, - reserved: { - privilege: { - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - description: i18n.translate('xpack.ml.feature.reserved.description', { - defaultMessage: - 'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.', - }), - }, - }); - - // Add server routes and initialize the plugin here - const commonRouteConfig = { - pre: [ - function forbidApiAccess() { - const licenseCheckResults = xpackMainPlugin.info - .feature(pluginId) - .getLicenseCheckResults(); - if (licenseCheckResults.isAvailable) { - return null; - } else { - throw Boom.forbidden(licenseCheckResults.message); - } - }, - ], - }; - - // Can access via new platform router's handler function 'context' parameter - context.ml.mlClient - const mlClient = core.elasticsearch.createClient('ml', { plugins: [elasticsearchJsPlugin] }); - http.registerRouteHandlerContext('ml', (context, request) => { - return { - mlClient: mlClient.asScoped(request), - }; - }); - - const routeInitializationDeps: RouteInitialization = { - commonRouteConfig, - route: http.route, - router: http.createRouter(), - elasticsearchPlugin: plugins.elasticsearch, - elasticsearchService: core.elasticsearch, - xpackMainPlugin: plugins.xpackMain, - spacesPlugin: plugins.spaces, - securityPlugin: plugins.security, - }; - - const extendedRouteInitializationDeps: RouteInitialization = { - ...routeInitializationDeps, - config: this.config, - savedObjects: coreSavedObjects, - spacesPlugin: plugins.spaces, - cloud: plugins.cloud, - }; - - const logInitializationDeps: LogInitialization = { - log: this.log, - }; - - annotationRoutes(routeInitializationDeps); - jobRoutes(routeInitializationDeps); - dataFeedRoutes(routeInitializationDeps); - dataFrameAnalyticsRoutes(routeInitializationDeps); - indicesRoutes(routeInitializationDeps); - jobValidationRoutes(extendedRouteInitializationDeps); - notificationRoutes(routeInitializationDeps); - systemRoutes(extendedRouteInitializationDeps); - dataRecognizer(extendedRouteInitializationDeps); - dataVisualizerRoutes(routeInitializationDeps); - calendars(routeInitializationDeps); - fieldsService(routeInitializationDeps); - filtersRoutes(routeInitializationDeps); - resultsServiceRoutes(routeInitializationDeps); - jobServiceRoutes(routeInitializationDeps); - jobAuditMessagesRoutes(routeInitializationDeps); - fileDataVisualizerRoutes(extendedRouteInitializationDeps); - - initMlServerLog(logInitializationDeps); - makeMlUsageCollector(plugins.usageCollection, coreSavedObjects); - } - - public stop() {} -} diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json new file mode 100644 index 00000000000000..e944af6821c0b6 --- /dev/null +++ b/x-pack/plugins/ml/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "ml", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["ml"], + "requiredPlugins": ["cloud", "features", "home", "licensing", "security", "spaces", "usageCollection"], + "server": true, + "ui": false +} diff --git a/x-pack/legacy/plugins/ml/server/client/__tests__/elasticsearch_ml.js b/x-pack/plugins/ml/server/client/__tests__/elasticsearch_ml.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/client/__tests__/elasticsearch_ml.js rename to x-pack/plugins/ml/server/client/__tests__/elasticsearch_ml.js diff --git a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js b/x-pack/plugins/ml/server/client/elasticsearch_ml.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js rename to x-pack/plugins/ml/server/client/elasticsearch_ml.js diff --git a/x-pack/legacy/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/client/error_wrapper.ts rename to x-pack/plugins/ml/server/client/error_wrapper.ts diff --git a/x-pack/legacy/plugins/ml/server/client/errors.js b/x-pack/plugins/ml/server/client/errors.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/client/errors.js rename to x-pack/plugins/ml/server/client/errors.js diff --git a/x-pack/legacy/plugins/ml/server/client/log.ts b/x-pack/plugins/ml/server/client/log.ts similarity index 94% rename from x-pack/legacy/plugins/ml/server/client/log.ts rename to x-pack/plugins/ml/server/client/log.ts index ae82383ead6050..8ee5882f6c2c16 100644 --- a/x-pack/legacy/plugins/ml/server/client/log.ts +++ b/x-pack/plugins/ml/server/client/log.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; export interface LogInitialization { log: Logger; diff --git a/x-pack/legacy/plugins/ml/server/new_platform/index.ts b/x-pack/plugins/ml/server/index.ts similarity index 57% rename from x-pack/legacy/plugins/ml/server/new_platform/index.ts rename to x-pack/plugins/ml/server/index.ts index b03f2dac613b03..55e87ed6f0c6a3 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/index.ts +++ b/x-pack/plugins/ml/server/index.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, MlInitializerContext } from './plugin'; +import { PluginInitializerContext } from 'kibana/server'; +import { MlServerPlugin } from './plugin'; -export function plugin(initializerContext: MlInitializerContext) { - return new Plugin(initializerContext); -} +export const plugin = (ctx: PluginInitializerContext) => new MlServerPlugin(ctx); diff --git a/x-pack/legacy/plugins/ml/server/lib/__tests__/query_utils.js b/x-pack/plugins/ml/server/lib/__tests__/query_utils.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/__tests__/query_utils.js rename to x-pack/plugins/ml/server/lib/__tests__/query_utils.js diff --git a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.d.ts b/x-pack/plugins/ml/server/lib/check_annotations/index.d.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/check_annotations/index.d.ts rename to x-pack/plugins/ml/server/lib/check_annotations/index.d.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.js b/x-pack/plugins/ml/server/lib/check_annotations/index.js similarity index 95% rename from x-pack/legacy/plugins/ml/server/lib/check_annotations/index.js rename to x-pack/plugins/ml/server/lib/check_annotations/index.js index 186c27b0326d70..55a90c0cec322c 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.js +++ b/x-pack/plugins/ml/server/lib/check_annotations/index.js @@ -10,7 +10,7 @@ import { ML_ANNOTATIONS_INDEX_ALIAS_READ, ML_ANNOTATIONS_INDEX_ALIAS_WRITE, ML_ANNOTATIONS_INDEX_PATTERN, -} from '../../../common/constants/index_patterns'; +} from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; // Annotations Feature is available if: // - ML_ANNOTATIONS_INDEX_PATTERN index is present diff --git a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.test.ts b/x-pack/plugins/ml/server/lib/check_license/check_license.test.ts similarity index 81% rename from x-pack/legacy/plugins/ml/server/lib/check_license/check_license.test.ts rename to x-pack/plugins/ml/server/lib/check_license/check_license.test.ts index 1d80a226486bb1..942dbe37226175 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.test.ts +++ b/x-pack/plugins/ml/server/lib/check_license/check_license.test.ts @@ -7,12 +7,12 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { set } from 'lodash'; -import { XPackInfo } from '../../../../xpack_main/server/lib/xpack_info'; +import { LicenseCheckResult } from '../../types'; import { checkLicense } from './check_license'; describe('check_license', () => { - let mockLicenseInfo: XPackInfo; - beforeEach(() => (mockLicenseInfo = {} as XPackInfo)); + let mockLicenseInfo: LicenseCheckResult; + beforeEach(() => (mockLicenseInfo = {} as LicenseCheckResult)); describe('license information is undefined', () => { it('should set isAvailable to false', () => { @@ -33,7 +33,9 @@ describe('check_license', () => { }); describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); + beforeEach(() => { + mockLicenseInfo.isAvailable = false; + }); it('should set isAvailable to false', () => { expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); @@ -54,8 +56,8 @@ describe('check_license', () => { describe('license information is available', () => { beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); + mockLicenseInfo.isAvailable = true; + mockLicenseInfo.type = 'basic'; }); describe('& ML is disabled in Elasticsearch', () => { @@ -66,7 +68,7 @@ describe('check_license', () => { sinon .stub() .withArgs('ml') - .returns({ isEnabled: () => false }) + .returns({ isEnabled: false }) ); }); @@ -89,21 +91,17 @@ describe('check_license', () => { describe('& ML is enabled in Elasticsearch', () => { beforeEach(() => { - set( - mockLicenseInfo, - 'feature', - sinon - .stub() - .withArgs('ml') - .returns({ isEnabled: () => true }) - ); + mockLicenseInfo.isEnabled = true; }); describe('& license is >= platinum', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - + beforeEach(() => { + mockLicenseInfo.type = 'platinum'; + }); describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); + beforeEach(() => { + mockLicenseInfo.isActive = true; + }); it('should set isAvailable to true', () => { expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); @@ -123,7 +121,9 @@ describe('check_license', () => { }); describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); + beforeEach(() => { + mockLicenseInfo.isActive = false; + }); it('should set isAvailable to true', () => { expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); @@ -144,10 +144,14 @@ describe('check_license', () => { }); describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => false)); + beforeEach(() => { + mockLicenseInfo.type = 'basic'; + }); describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); + beforeEach(() => { + mockLicenseInfo.isActive = true; + }); it('should set isAvailable to true', () => { expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); diff --git a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.ts b/x-pack/plugins/ml/server/lib/check_license/check_license.ts similarity index 75% rename from x-pack/legacy/plugins/ml/server/lib/check_license/check_license.ts rename to x-pack/plugins/ml/server/lib/check_license/check_license.ts index c88ab087a81985..5bf3d590a1912d 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.ts +++ b/x-pack/plugins/ml/server/lib/check_license/check_license.ts @@ -5,8 +5,11 @@ */ import { i18n } from '@kbn/i18n'; -import { LICENSE_TYPE } from '../../../common/constants/license'; -import { XPackInfo } from '../../../../../../legacy/plugins/xpack_main/server/lib/xpack_info'; +import { + LICENSE_TYPE, + VALID_FULL_LICENSE_MODES, +} from '../../../../../legacy/plugins/ml/common/constants/license'; +import { LicenseCheckResult } from '../../types'; interface Response { isAvailable: boolean; @@ -17,10 +20,10 @@ interface Response { message?: string; } -export function checkLicense(xpackLicenseInfo: XPackInfo): Response { +export function checkLicense(licenseCheckResult: LicenseCheckResult): Response { // If, for some reason, we cannot get the license information // from Elasticsearch, assume worst case and disable the Machine Learning UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { + if (licenseCheckResult === undefined || !licenseCheckResult.isAvailable) { return { isAvailable: false, showLinks: true, @@ -35,7 +38,7 @@ export function checkLicense(xpackLicenseInfo: XPackInfo): Response { }; } - const featureEnabled = xpackLicenseInfo.feature('ml').isEnabled(); + const featureEnabled = licenseCheckResult.isEnabled; if (!featureEnabled) { return { isAvailable: false, @@ -47,12 +50,11 @@ export function checkLicense(xpackLicenseInfo: XPackInfo): Response { }; } - const VALID_FULL_LICENSE_MODES = ['platinum', 'enterprise', 'trial']; - - const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_FULL_LICENSE_MODES); + const isLicenseModeValid = + licenseCheckResult.type && VALID_FULL_LICENSE_MODES.includes(licenseCheckResult.type); const licenseType = isLicenseModeValid === true ? LICENSE_TYPE.FULL : LICENSE_TYPE.BASIC; - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseTypeName = xpackLicenseInfo.license.getType(); + const isLicenseActive = licenseCheckResult.isActive; + const licenseTypeName = licenseCheckResult.type; // Platinum or trial license is valid but not active, i.e. expired if (licenseType === LICENSE_TYPE.FULL && isLicenseActive === false) { diff --git a/x-pack/legacy/plugins/ml/server/lib/check_license/index.ts b/x-pack/plugins/ml/server/lib/check_license/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/check_license/index.ts rename to x-pack/plugins/ml/server/lib/check_license/index.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts b/x-pack/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts rename to x-pack/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.test.ts b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.test.ts similarity index 91% rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.test.ts rename to x-pack/plugins/ml/server/lib/check_privileges/check_privileges.test.ts index da8ef25b2f4dff..0690aa53576a5a 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.test.ts +++ b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.test.ts @@ -8,81 +8,29 @@ import { callWithRequestProvider } from './__mocks__/call_with_request'; import { privilegesProvider } from './check_privileges'; import { mlPrivileges } from './privileges'; -const xpackMainPluginWithSecurity = { - info: { - isAvailable: () => true, - feature: (f: string) => { - switch (f) { - case 'ml': - return { isEnabled: () => true }; - case 'security': - return { isEnabled: () => true }; - } - }, - license: { - isOneOf: () => true, - isActive: () => true, - getType: () => 'platinum', - }, - }, -} as any; +const licenseCheckResultWithSecurity = { + isAvailable: true, + isEnabled: true, + isSecurityDisabled: false, + type: 'platinum', + isActive: true, +}; -const xpackMainPluginWithOutSecurity = { - info: { - isAvailable: () => true, - feature: (f: string) => { - switch (f) { - case 'ml': - return { isEnabled: () => true }; - case 'security': - return { isEnabled: () => false }; - } - }, - license: { - isOneOf: () => true, - isActive: () => true, - getType: () => 'platinum', - }, - }, -} as any; +const licenseCheckResultWithOutSecurity = { + ...licenseCheckResultWithSecurity, + isSecurityDisabled: true, +}; -const xpackMainPluginWithOutSecurityBasicLicense = { - info: { - isAvailable: () => true, - feature: (f: string) => { - switch (f) { - case 'ml': - return { isEnabled: () => true }; - case 'security': - return { isEnabled: () => false }; - } - }, - license: { - isOneOf: () => false, - isActive: () => true, - getType: () => 'basic', - }, - }, -} as any; +const licenseCheckResultWithOutSecurityBasicLicense = { + ...licenseCheckResultWithSecurity, + isSecurityDisabled: true, + type: 'basic', +}; -const xpackMainPluginWithSecurityBasicLicense = { - info: { - isAvailable: () => true, - feature: (f: string) => { - switch (f) { - case 'ml': - return { isEnabled: () => true }; - case 'security': - return { isEnabled: () => true }; - } - }, - license: { - isOneOf: () => false, - isActive: () => true, - getType: () => 'basic', - }, - }, -} as any; +const licenseCheckResultWithSecurityBasicLicense = { + ...licenseCheckResultWithSecurity, + type: 'basic', +}; const mlIsEnabled = async () => true; const mlIsNotEnabled = async () => false; @@ -99,7 +47,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('partialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurity, + licenseCheckResultWithSecurity, mlIsEnabled ); const { capabilities } = await getPrivileges(); @@ -114,7 +62,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('partialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurity, + licenseCheckResultWithSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -149,7 +97,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('fullPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurity, + licenseCheckResultWithSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -184,7 +132,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurity, + licenseCheckResultWithSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -219,7 +167,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurity, + licenseCheckResultWithSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -254,7 +202,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('partialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurityBasicLicense, + licenseCheckResultWithSecurityBasicLicense, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -289,7 +237,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('fullPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurityBasicLicense, + licenseCheckResultWithSecurityBasicLicense, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -324,7 +272,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('fullPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithSecurity, + licenseCheckResultWithSecurity, mlIsNotEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -361,7 +309,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('partialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithOutSecurity, + licenseCheckResultWithOutSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -396,7 +344,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithOutSecurity, + licenseCheckResultWithOutSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -431,7 +379,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithOutSecurity, + licenseCheckResultWithOutSecurity, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -466,7 +414,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('partialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithOutSecurityBasicLicense, + licenseCheckResultWithOutSecurityBasicLicense, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -501,7 +449,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('fullPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithOutSecurityBasicLicense, + licenseCheckResultWithOutSecurityBasicLicense, mlIsEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); @@ -536,7 +484,7 @@ describe('check_privileges', () => { const callWithRequest = callWithRequestProvider('partialPrivileges'); const { getPrivileges } = privilegesProvider( callWithRequest, - xpackMainPluginWithOutSecurity, + licenseCheckResultWithOutSecurity, mlIsNotEnabled ); const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges(); diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.ts similarity index 94% rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts rename to x-pack/plugins/ml/server/lib/check_privileges/check_privileges.ts index 617778afbe121b..a427780d13344a 100644 --- a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts +++ b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.ts @@ -5,12 +5,14 @@ */ import { IScopedClusterClient } from 'kibana/server'; -import { Privileges, getDefaultPrivileges } from '../../../common/types/privileges'; -import { XPackMainPlugin } from '../../../../xpack_main/server/xpack_main'; -import { isSecurityDisabled } from '../../lib/security_utils'; +import { + Privileges, + getDefaultPrivileges, +} from '../../../../../legacy/plugins/ml/common/types/privileges'; import { upgradeCheckProvider } from './upgrade'; import { checkLicense } from '../check_license'; -import { LICENSE_TYPE } from '../../../common/constants/license'; +import { LICENSE_TYPE } from '../../../../../legacy/plugins/ml/common/constants/license'; +import { LicenseCheckResult } from '../../types'; import { mlPrivileges } from './privileges'; @@ -25,7 +27,7 @@ interface Response { export function privilegesProvider( callAsCurrentUser: IScopedClusterClient['callAsCurrentUser'], - xpackMainPlugin: XPackMainPlugin, + licenseCheckResult: LicenseCheckResult, isMlEnabledInSpace: () => Promise, ignoreSpaces: boolean = false ) { @@ -35,8 +37,8 @@ export function privilegesProvider( const privileges = getDefaultPrivileges(); const upgradeInProgress = await isUpgradeInProgress(); - const securityDisabled = isSecurityDisabled(xpackMainPlugin); - const license = checkLicense(xpackMainPlugin.info); + const securityDisabled = licenseCheckResult.isSecurityDisabled; + const license = checkLicense(licenseCheckResult); const isPlatinumOrTrialLicense = license.licenseType === LICENSE_TYPE.FULL; const mlFeatureEnabledInSpace = await isMlEnabledInSpace(); diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/index.ts b/x-pack/plugins/ml/server/lib/check_privileges/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/index.ts rename to x-pack/plugins/ml/server/lib/check_privileges/index.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/privileges.ts b/x-pack/plugins/ml/server/lib/check_privileges/privileges.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/privileges.ts rename to x-pack/plugins/ml/server/lib/check_privileges/privileges.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/upgrade.ts b/x-pack/plugins/ml/server/lib/check_privileges/upgrade.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/upgrade.ts rename to x-pack/plugins/ml/server/lib/check_privileges/upgrade.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/index.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/index.ts rename to x-pack/plugins/ml/server/lib/ml_telemetry/index.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts rename to x-pack/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts similarity index 85% rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts rename to x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts index 9d14ffb31be631..c03396445f8687 100644 --- a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts +++ b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts @@ -55,10 +55,9 @@ describe('ml_telemetry', () => { }); describe('incrementFileDataVisualizerIndexCreationCount', () => { - let savedObjects: any; - let internalRepository: any; + let savedObjectsClient: any; - function createInternalRepositoryInstance( + function createSavedObjectsClientInstance( telemetryEnabled?: boolean, indexCreationCount?: number ) { @@ -93,42 +92,39 @@ describe('ml_telemetry', () => { } function mockInit(telemetryEnabled?: boolean, indexCreationCount?: number): void { - internalRepository = createInternalRepositoryInstance(telemetryEnabled, indexCreationCount); - savedObjects = { - createInternalRepository: jest.fn(() => internalRepository), - }; + savedObjectsClient = createSavedObjectsClientInstance(telemetryEnabled, indexCreationCount); } it('should not increment if telemetry status cannot be determined', async () => { mockInit(); - await incrementFileDataVisualizerIndexCreationCount(savedObjects); + await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient); - expect(internalRepository.create.mock.calls).toHaveLength(0); + expect(savedObjectsClient.create.mock.calls).toHaveLength(0); }); it('should not increment if telemetry status is disabled', async () => { mockInit(false); - await incrementFileDataVisualizerIndexCreationCount(savedObjects); + await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient); - expect(internalRepository.create.mock.calls).toHaveLength(0); + expect(savedObjectsClient.create.mock.calls).toHaveLength(0); }); it('should initialize index_creation_count with 1', async () => { mockInit(true); - await incrementFileDataVisualizerIndexCreationCount(savedObjects); + await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient); - expect(internalRepository.create.mock.calls[0][0]).toBe('ml-telemetry'); - expect(internalRepository.create.mock.calls[0][1]).toEqual({ + expect(savedObjectsClient.create.mock.calls[0][0]).toBe('ml-telemetry'); + expect(savedObjectsClient.create.mock.calls[0][1]).toEqual({ file_data_visualizer: { index_creation_count: 1 }, }); }); it('should increment index_creation_count to 2', async () => { mockInit(true, 1); - await incrementFileDataVisualizerIndexCreationCount(savedObjects); + await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient); - expect(internalRepository.create.mock.calls[0][0]).toBe('ml-telemetry'); - expect(internalRepository.create.mock.calls[0][1]).toEqual({ + expect(savedObjectsClient.create.mock.calls[0][0]).toBe('ml-telemetry'); + expect(savedObjectsClient.create.mock.calls[0][1]).toEqual({ file_data_visualizer: { index_creation_count: 2 }, }); }); diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts similarity index 74% rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts rename to x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts index d76b1ee94e21e9..8cf24213961b18 100644 --- a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts +++ b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SavedObjectAttributes, - SavedObjectsServiceStart, - ISavedObjectsRepository, -} from 'src/core/server'; +import { SavedObjectAttributes, SavedObjectsClientContract } from 'src/core/server'; export interface MlTelemetry extends SavedObjectAttributes { file_data_visualizer: { @@ -31,21 +27,20 @@ export function createMlTelemetry(count: number = 0): MlTelemetry { } // savedObjects export function storeMlTelemetry( - internalRepository: ISavedObjectsRepository, + savedObjectsClient: SavedObjectsClientContract, mlTelemetry: MlTelemetry ): void { - internalRepository.create('ml-telemetry', mlTelemetry, { + savedObjectsClient.create('ml-telemetry', mlTelemetry, { id: ML_TELEMETRY_DOC_ID, overwrite: true, }); } export async function incrementFileDataVisualizerIndexCreationCount( - savedObjects: SavedObjectsServiceStart + savedObjectsClient: SavedObjectsClientContract ): Promise { - const internalRepository = await savedObjects.createInternalRepository(); try { - const { attributes } = await internalRepository.get('telemetry', 'telemetry'); + const { attributes } = await savedObjectsClient.get('telemetry', 'telemetry'); if (attributes.enabled === false) { return; @@ -59,7 +54,7 @@ export async function incrementFileDataVisualizerIndexCreationCount( let indicesCount = 1; try { - const { attributes } = (await internalRepository.get( + const { attributes } = (await savedObjectsClient.get( 'ml-telemetry', ML_TELEMETRY_DOC_ID )) as MlTelemetrySavedObject; @@ -69,5 +64,5 @@ export async function incrementFileDataVisualizerIndexCreationCount( } const mlTelemetry = createMlTelemetry(indicesCount); - storeMlTelemetry(internalRepository, mlTelemetry); + storeMlTelemetry(savedObjectsClient, mlTelemetry); } diff --git a/x-pack/legacy/plugins/ml/server/lib/query_utils.ts b/x-pack/plugins/ml/server/lib/query_utils.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/query_utils.ts rename to x-pack/plugins/ml/server/lib/query_utils.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/sample_data_sets/index.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/sample_data_sets/index.ts rename to x-pack/plugins/ml/server/lib/sample_data_sets/index.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts rename to x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts diff --git a/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts b/x-pack/plugins/ml/server/lib/spaces_utils.ts similarity index 75% rename from x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts rename to x-pack/plugins/ml/server/lib/spaces_utils.ts index 92373bae4ea1d0..ed684eadb95704 100644 --- a/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts +++ b/x-pack/plugins/ml/server/lib/spaces_utils.ts @@ -5,20 +5,19 @@ */ import { Request } from 'hapi'; -import { Space } from '../../../../../plugins/spaces/server'; -import { LegacySpacesPlugin } from '../../../spaces'; +import { Space, SpacesPluginSetup } from '../../../spaces/server'; interface GetActiveSpaceResponse { valid: boolean; space?: Space; } -export function spacesUtilsProvider(spacesPlugin: LegacySpacesPlugin, request: Request) { +export function spacesUtilsProvider(spacesPlugin: SpacesPluginSetup, request: Request) { async function activeSpace(): Promise { try { return { valid: true, - space: await spacesPlugin.getActiveSpace(request), + space: await spacesPlugin.spacesService.getActiveSpace(request), }; } catch (e) { return { diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json b/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json rename to x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json b/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json rename to x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.test.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts similarity index 96% rename from x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.test.ts rename to x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts index 7e0649d15bfb0e..d7a13154a6f378 100644 --- a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.test.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts @@ -8,9 +8,12 @@ import getAnnotationsRequestMock from './__mocks__/get_annotations_request.json' import getAnnotationsResponseMock from './__mocks__/get_annotations_response.json'; import { RequestHandlerContext } from 'src/core/server'; -import { ANNOTATION_TYPE } from '../../../common/constants/annotations'; -import { ML_ANNOTATIONS_INDEX_ALIAS_WRITE } from '../../../common/constants/index_patterns'; -import { Annotation, isAnnotations } from '../../../common/types/annotations'; +import { ANNOTATION_TYPE } from '../../../../../legacy/plugins/ml/common/constants/annotations'; +import { ML_ANNOTATIONS_INDEX_ALIAS_WRITE } from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; +import { + Annotation, + isAnnotations, +} from '../../../../../legacy/plugins/ml/common/types/annotations'; import { DeleteParams, GetResponse, IndexAnnotationArgs } from './annotation'; import { annotationServiceProvider } from './index'; diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts similarity index 96% rename from x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.ts rename to x-pack/plugins/ml/server/models/annotation_service/annotation.ts index 399305ea2603eb..042d7bbc80653a 100644 --- a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts @@ -8,18 +8,18 @@ import Boom from 'boom'; import _ from 'lodash'; import { RequestHandlerContext } from 'src/core/server'; -import { ANNOTATION_TYPE } from '../../../common/constants/annotations'; +import { ANNOTATION_TYPE } from '../../../../../legacy/plugins/ml/common/constants/annotations'; import { ML_ANNOTATIONS_INDEX_ALIAS_READ, ML_ANNOTATIONS_INDEX_ALIAS_WRITE, -} from '../../../common/constants/index_patterns'; +} from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; import { Annotation, Annotations, isAnnotation, isAnnotations, -} from '../../../common/types/annotations'; +} from '../../../../../legacy/plugins/ml/common/types/annotations'; // TODO All of the following interface/type definitions should // eventually be replaced by the proper upstream definitions diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/index.ts b/x-pack/plugins/ml/server/models/annotation_service/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/annotation_service/index.ts rename to x-pack/plugins/ml/server/models/annotation_service/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js rename to x-pack/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts similarity index 75% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts rename to x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts index ea986feab4e997..e39a0177c31b95 100644 --- a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts @@ -5,10 +5,10 @@ */ import { APICaller } from 'src/core/server'; -import { BucketSpanEstimatorData } from '../../../public/application/services/ml_api_service'; +import { BucketSpanEstimatorData } from '../../../../../legacy/plugins/ml/public/application/services/ml_api_service'; export function estimateBucketSpanFactory( callAsCurrentUser: APICaller, callAsInternalUser: APICaller, - xpackMainPlugin: any + isSecurityDisabled: boolean ): (config: BucketSpanEstimatorData) => Promise; diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js similarity index 98% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js rename to x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js index aec677dd57d614..53b9d75304963b 100644 --- a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js @@ -12,9 +12,11 @@ import { INTERVALS } from './intervals'; import { singleSeriesCheckerFactory } from './single_series_checker'; import { polledDataCheckerFactory } from './polled_data_checker'; -import { isSecurityDisabled } from '../../lib/security_utils'; - -export function estimateBucketSpanFactory(callAsCurrentUser, callAsInternalUser, xpackMainPlugin) { +export function estimateBucketSpanFactory( + callAsCurrentUser, + callAsInternalUser, + isSecurityDisabled +) { const PolledDataChecker = polledDataCheckerFactory(callAsCurrentUser); const SingleSeriesChecker = singleSeriesCheckerFactory(callAsCurrentUser); @@ -384,7 +386,7 @@ export function estimateBucketSpanFactory(callAsCurrentUser, callAsInternalUser, }); } - if (isSecurityDisabled(xpackMainPlugin)) { + if (isSecurityDisabled) { getBucketSpanEstimation(); } else { // if security is enabled, check that the user has permission to diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/index.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/index.ts rename to x-pack/plugins/ml/server/models/bucket_span_estimator/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/intervals.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/intervals.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/intervals.js rename to x-pack/plugins/ml/server/models/bucket_span_estimator/intervals.js diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js rename to x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js rename to x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js diff --git a/x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts rename to x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts diff --git a/x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js rename to x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js diff --git a/x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/index.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/index.ts rename to x-pack/plugins/ml/server/models/calculate_model_memory_limit/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.ts rename to x-pack/plugins/ml/server/models/calendar/calendar_manager.ts diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts b/x-pack/plugins/ml/server/models/calendar/event_manager.ts similarity index 94% rename from x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts rename to x-pack/plugins/ml/server/models/calendar/event_manager.ts index 488839f68b3fe2..0a3108016da0e3 100644 --- a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/event_manager.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; -import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; +import { GLOBAL_CALENDAR } from '../../../../../legacy/plugins/ml/common/constants/calendars'; export interface CalendarEvent { calendar_id?: string; diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/index.ts b/x-pack/plugins/ml/server/models/calendar/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/calendar/index.ts rename to x-pack/plugins/ml/server/models/calendar/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts similarity index 88% rename from x-pack/legacy/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts rename to x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts index abe389165182f0..a8757e289dcf75 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { callWithRequestType } from '../../../common/types/kibana'; -import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns'; -import { JobMessage } from '../../../common/types/audit_message'; +import { callWithRequestType } from '../../../../../legacy/plugins/ml/common/types/kibana'; +import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; +import { JobMessage } from '../../../../../legacy/plugins/ml/common/types/audit_message'; const SIZE = 50; diff --git a/x-pack/legacy/plugins/ml/server/models/data_frame_analytics/index.js b/x-pack/plugins/ml/server/models/data_frame_analytics/index.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_frame_analytics/index.js rename to x-pack/plugins/ml/server/models/data_frame_analytics/index.js diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts similarity index 97% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts rename to x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts index de23950e5cc1c8..c51f65714bc055 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts @@ -5,7 +5,7 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { Module } from '../../../common/types/modules'; +import { Module } from '../../../../../legacy/plugins/ml/common/types/modules'; import { DataRecognizer } from '../data_recognizer'; describe('ML - data recognizer', () => { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts similarity index 99% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts rename to x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 553de75e38e05d..8d2a6c9955da36 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -10,7 +10,7 @@ import numeral from '@elastic/numeral'; import { CallAPIOptions, RequestHandlerContext, SavedObjectsClientContract } from 'kibana/server'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { merge } from 'lodash'; -import { MlJob } from '../../../common/types/jobs'; +import { MlJob } from '../../../../../legacy/plugins/ml/common/types/jobs'; import { KibanaObjects, ModuleDataFeed, @@ -23,8 +23,11 @@ import { JobResponse, KibanaObjectResponse, DataRecognizerConfigResponse, -} from '../../../common/types/modules'; -import { getLatestDataOrBucketTimestamp, prefixDatafeedId } from '../../../common/util/job_utils'; +} from '../../../../../legacy/plugins/ml/common/types/modules'; +import { + getLatestDataOrBucketTimestamp, + prefixDatafeedId, +} from '../../../../../legacy/plugins/ml/common/util/job_utils'; import { mlLog } from '../../client/log'; // @ts-ignore import { jobServiceProvider } from '../job_service'; diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/index.ts b/x-pack/plugins/ml/server/models/data_recognizer/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/index.ts rename to x-pack/plugins/ml/server/models/data_recognizer/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json diff --git a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts similarity index 99% rename from x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.ts rename to x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index b0a61b1232dc0b..9463f74e1e746e 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -6,8 +6,8 @@ import { CallAPIOptions, IScopedClusterClient } from 'src/core/server'; import _ from 'lodash'; -import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types'; -import { getSafeAggregationName } from '../../../common/util/job_utils'; +import { ML_JOB_FIELD_TYPES } from '../../../../../legacy/plugins/ml/common/constants/field_types'; +import { getSafeAggregationName } from '../../../../../legacy/plugins/ml/common/util/job_utils'; import { buildBaseFilterCriteria, buildSamplerAggregation, diff --git a/x-pack/legacy/plugins/ml/server/models/data_visualizer/index.ts b/x-pack/plugins/ml/server/models/data_visualizer/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/data_visualizer/index.ts rename to x-pack/plugins/ml/server/models/data_visualizer/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.d.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.d.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.d.ts rename to x-pack/plugins/ml/server/models/fields_service/fields_service.d.ts diff --git a/x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.js b/x-pack/plugins/ml/server/models/fields_service/fields_service.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.js rename to x-pack/plugins/ml/server/models/fields_service/fields_service.js diff --git a/x-pack/legacy/plugins/ml/server/models/fields_service/index.ts b/x-pack/plugins/ml/server/models/fields_service/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/fields_service/index.ts rename to x-pack/plugins/ml/server/models/fields_service/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts similarity index 95% rename from x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts rename to x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index 9f30f609c60b63..1d0452f2337f95 100644 --- a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { RequestHandlerContext } from 'kibana/server'; -import { FindFileStructureResponse } from '../../../common/types/file_datavisualizer'; +import { FindFileStructureResponse } from '../../../../../legacy/plugins/ml/common/types/file_datavisualizer'; export type InputData = any[]; diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts similarity index 97% rename from x-pack/legacy/plugins/ml/server/models/file_data_visualizer/import_data.ts rename to x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts index 008efb43a6c07e..e4de71ad0793d2 100644 --- a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/import_data.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts @@ -5,7 +5,7 @@ */ import { RequestHandlerContext } from 'kibana/server'; -import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer'; +import { INDEX_META_DATA_CREATED_BY } from '../../../../../legacy/plugins/ml/common/constants/file_datavisualizer'; import { InputData } from './file_data_visualizer'; export interface Settings { diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/index.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/file_data_visualizer/index.ts rename to x-pack/plugins/ml/server/models/file_data_visualizer/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts similarity index 98% rename from x-pack/legacy/plugins/ml/server/models/filter/filter_manager.ts rename to x-pack/plugins/ml/server/models/filter/filter_manager.ts index f40663a5eb6b2d..baba495257acac 100644 --- a/x-pack/legacy/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -7,7 +7,10 @@ import Boom from 'boom'; import { IScopedClusterClient } from 'src/core/server'; -import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; +import { + DetectorRule, + DetectorRuleScope, +} from '../../../../../legacy/plugins/ml/common/types/detector_rules'; export interface Filter { filter_id: string; diff --git a/x-pack/legacy/plugins/ml/server/models/filter/index.js b/x-pack/plugins/ml/server/models/filter/index.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/filter/index.js rename to x-pack/plugins/ml/server/models/filter/index.js diff --git a/x-pack/legacy/plugins/ml/server/models/filter/index.ts b/x-pack/plugins/ml/server/models/filter/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/filter/index.ts rename to x-pack/plugins/ml/server/models/filter/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/index.ts b/x-pack/plugins/ml/server/models/job_audit_messages/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_audit_messages/index.ts rename to x-pack/plugins/ml/server/models/job_audit_messages/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts rename to x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js similarity index 98% rename from x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js rename to x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index 2cdfc0ef4f4c5f..b434846d6f0f4c 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns'; +import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; import moment from 'moment'; const SIZE = 1000; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/datafeeds.js b/x-pack/plugins/ml/server/models/job_service/datafeeds.js similarity index 97% rename from x-pack/legacy/plugins/ml/server/models/job_service/datafeeds.js rename to x-pack/plugins/ml/server/models/job_service/datafeeds.js index c3b54fff0682d8..961b712610512d 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/datafeeds.js +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.js @@ -5,7 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; +import { + JOB_STATE, + DATAFEED_STATE, +} from '../../../../../legacy/plugins/ml/common/constants/states'; import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils'; export function datafeedsProvider(callWithRequest) { diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/error_utils.js b/x-pack/plugins/ml/server/models/job_service/error_utils.js similarity index 94% rename from x-pack/legacy/plugins/ml/server/models/job_service/error_utils.js rename to x-pack/plugins/ml/server/models/job_service/error_utils.js index 6f25b5870f85ce..21e45110e70930 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/error_utils.js +++ b/x-pack/plugins/ml/server/models/job_service/error_utils.js @@ -5,7 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; +import { + JOB_STATE, + DATAFEED_STATE, +} from '../../../../../legacy/plugins/ml/common/constants/states'; const REQUEST_TIMEOUT = 'RequestTimeout'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js b/x-pack/plugins/ml/server/models/job_service/groups.js similarity index 95% rename from x-pack/legacy/plugins/ml/server/models/job_service/groups.js rename to x-pack/plugins/ml/server/models/job_service/groups.js index 6fbc071ef9854f..b30e9cdc6048b7 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js +++ b/x-pack/plugins/ml/server/models/job_service/groups.js @@ -5,7 +5,7 @@ */ import { CalendarManager } from '../calendar'; -import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; +import { GLOBAL_CALENDAR } from '../../../../../legacy/plugins/ml/common/constants/calendars'; export function groupsProvider(callWithRequest) { const calMngr = new CalendarManager(callWithRequest); diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/index.js b/x-pack/plugins/ml/server/models/job_service/index.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/index.js rename to x-pack/plugins/ml/server/models/job_service/index.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js b/x-pack/plugins/ml/server/models/job_service/jobs.js similarity index 98% rename from x-pack/legacy/plugins/ml/server/models/job_service/jobs.js rename to x-pack/plugins/ml/server/models/job_service/jobs.js index b4b476c1f926ea..16d3c30bb0a280 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js +++ b/x-pack/plugins/ml/server/models/job_service/jobs.js @@ -5,7 +5,10 @@ */ import { i18n } from '@kbn/i18n'; -import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; +import { + JOB_STATE, + DATAFEED_STATE, +} from '../../../../../legacy/plugins/ml/common/constants/states'; import { datafeedsProvider } from './datafeeds'; import { jobAuditMessagesProvider } from '../job_audit_messages'; import { resultsServiceProvider } from '../results_service'; @@ -14,7 +17,7 @@ import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils'; import { getLatestDataOrBucketTimestamp, isTimeSeriesViewJob, -} from '../../../common/util/job_utils'; +} from '../../../../../legacy/plugins/ml/common/util/job_utils'; import { groupsProvider } from './groups'; import { uniq } from 'lodash'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts similarity index 95% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index ea2c71b04f56d2..1a098fdf16bb7c 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -6,13 +6,13 @@ import { chunk } from 'lodash'; import { SearchResponse } from 'elasticsearch'; -import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/new_job'; +import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../../../legacy/plugins/ml/common/constants/new_job'; import { Token, CategorizationAnalyzer, CategoryFieldExample, -} from '../../../../../common/types/categories'; -import { callWithRequestType } from '../../../../../common/types/kibana'; +} from '../../../../../../../legacy/plugins/ml/common/types/categories'; +import { callWithRequestType } from '../../../../../../../legacy/plugins/ml/common/types/kibana'; import { ValidationResults } from './validation_results'; const CHUNK_SIZE = 100; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts similarity index 92% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts index 3361cc454e2b7b..c8eb0002a31c80 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -5,9 +5,12 @@ */ import { SearchResponse } from 'elasticsearch'; -import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns'; -import { CategoryId, Category } from '../../../../../common/types/categories'; -import { callWithRequestType } from '../../../../../common/types/kibana'; +import { ML_RESULTS_INDEX_PATTERN } from '../../../../../../../legacy/plugins/ml/common/constants/index_patterns'; +import { + CategoryId, + Category, +} from '../../../../../../../legacy/plugins/ml/common/types/categories'; +import { callWithRequestType } from '../../../../../../../legacy/plugins/ml/common/types/kibana'; export function topCategoriesProvider(callWithRequest: callWithRequestType) { async function getTotalCategories(jobId: string): Promise<{ total: number }> { diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts similarity index 96% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts index 34e63eabb405ef..bb1106b4d6396f 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts @@ -9,13 +9,13 @@ import { CATEGORY_EXAMPLES_VALIDATION_STATUS, CATEGORY_EXAMPLES_ERROR_LIMIT, CATEGORY_EXAMPLES_WARNING_LIMIT, -} from '../../../../../common/constants/new_job'; +} from '../../../../../../../legacy/plugins/ml/common/constants/new_job'; import { FieldExampleCheck, CategoryFieldExample, VALIDATION_RESULT, -} from '../../../../../common/types/categories'; -import { getMedianStringLength } from '../../../../../common/util/string_utils'; +} from '../../../../../../../legacy/plugins/ml/common/types/categories'; +import { getMedianStringLength } from '../../../../../../../legacy/plugins/ml/common/util/string_utils'; const VALID_TOKEN_COUNT = 3; const MEDIAN_LINE_LENGTH_LIMIT = 400; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts b/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts similarity index 87% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/charts.ts index 88ae8caa91e4a1..e662e3ca03ded6 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts @@ -6,7 +6,7 @@ import { newJobLineChartProvider } from './line_chart'; import { newJobPopulationChartProvider } from './population_chart'; -import { callWithRequestType } from '../../../../common/types/kibana'; +import { callWithRequestType } from '../../../../../../legacy/plugins/ml/common/types/kibana'; export function newJobChartsProvider(callWithRequest: callWithRequestType) { const { newJobLineChart } = newJobLineChartProvider(callWithRequest); diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts b/x-pack/plugins/ml/server/models/job_service/new_job/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts similarity index 92% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts index c1a5ad5e38ecc9..3dfe935c655d5c 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts @@ -5,9 +5,12 @@ */ import { get } from 'lodash'; -import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; -import { callWithRequestType } from '../../../../common/types/kibana'; -import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; +import { + AggFieldNamePair, + EVENT_RATE_FIELD_ID, +} from '../../../../../../legacy/plugins/ml/common/types/fields'; +import { callWithRequestType } from '../../../../../../legacy/plugins/ml/common/types/kibana'; +import { ML_MEDIAN_PERCENTS } from '../../../../../../legacy/plugins/ml/common/util/job_utils'; type DtrIndex = number; type TimeStamp = number; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts similarity index 95% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts rename to x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index ee35f13c44ee60..d1ef9773f8f179 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -5,9 +5,12 @@ */ import { get } from 'lodash'; -import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; -import { callWithRequestType } from '../../../../common/types/kibana'; -import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; +import { + AggFieldNamePair, + EVENT_RATE_FIELD_ID, +} from '../../../../../../legacy/plugins/ml/common/types/fields'; +import { callWithRequestType } from '../../../../../../legacy/plugins/ml/common/types/kibana'; +import { ML_MEDIAN_PERCENTS } from '../../../../../../legacy/plugins/ml/common/util/job_utils'; const OVER_FIELD_EXAMPLES_COUNT = 40; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts similarity index 97% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts index efe06f8b5ad4ab..475612f276c724 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts @@ -4,12 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Aggregation, METRIC_AGG_TYPE } from '../../../../common/types/fields'; +import { + Aggregation, + METRIC_AGG_TYPE, +} from '../../../../../../legacy/plugins/ml/common/types/fields'; import { ML_JOB_AGGREGATION, KIBANA_AGGREGATION, ES_AGGREGATION, -} from '../../../../common/constants/aggregation_types'; +} from '../../../../../../legacy/plugins/ml/common/constants/aggregation_types'; // aggregation object missing id, title and fields and has null for kibana and dsl aggregation names. // this is used as the basis for the ML only aggregations diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts similarity index 96% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index 5827201a636619..446c71dd40f684 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -12,9 +12,9 @@ import { FieldId, NewJobCaps, METRIC_AGG_TYPE, -} from '../../../../common/types/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../../src/plugins/data/server'; -import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; +} from '../../../../../../legacy/plugins/ml/common/types/fields'; +import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/server'; +import { ML_JOB_AGGREGATION } from '../../../../../../legacy/plugins/ml/common/constants/aggregation_types'; import { rollupServiceProvider, RollupJob, RollupFields } from './rollup'; import { aggregations, mlOnlyAggregations } from './aggregations'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/index.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/index.ts rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts similarity index 93% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts index 3a9d979ccb22ca..0a967c760a1932 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts @@ -5,7 +5,11 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { Aggregation, Field, NewJobCaps } from '../../../../common/types/fields'; +import { + Aggregation, + Field, + NewJobCaps, +} from '../../../../../../legacy/plugins/ml/common/types/fields'; import { fieldServiceProvider } from './field_service'; interface NewJobCapsResponse { diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts similarity index 92% rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts index 1e9ce3d8d50225..4cbdfe4f360e03 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts @@ -7,8 +7,8 @@ import { SavedObject } from 'src/core/server'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { SavedObjectsClientContract } from 'kibana/server'; -import { FieldId } from '../../../../common/types/fields'; -import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; +import { FieldId } from '../../../../../../legacy/plugins/ml/common/types/fields'; +import { ES_AGGREGATION } from '../../../../../../legacy/plugins/ml/common/constants/aggregation_types'; export type RollupFields = Record]>; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/job_validation.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/job_validation.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/job_validation.js rename to x-pack/plugins/ml/server/models/job_validation/__tests__/job_validation.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js similarity index 98% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js index 3dc2bee1e8705f..023e0f5b614ed8 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js +++ b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { validateBucketSpan } from '../validate_bucket_span'; -import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../../common/constants/validation'; +import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../../../../legacy/plugins/ml/common/constants/validation'; // farequote2017 snapshot snapshot mock search response // it returns a mock for the response of PolledDataChecker's search request diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/index.ts b/x-pack/plugins/ml/server/models/job_validation/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/index.ts rename to x-pack/plugins/ml/server/models/job_validation/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.d.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.d.ts similarity index 83% rename from x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.d.ts rename to x-pack/plugins/ml/server/models/job_validation/job_validation.d.ts index 4580602b0af238..bb8a372eaba30c 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.d.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.d.ts @@ -6,7 +6,7 @@ import { APICaller } from 'src/core/server'; import { TypeOf } from '@kbn/config-schema'; -import { validateJobSchema } from '../../new_platform/job_validation_schema'; +import { validateJobSchema } from '../../routes/schemas/job_validation_schema'; type ValidateJobPayload = TypeOf; @@ -15,5 +15,5 @@ export function validateJob( payload: ValidateJobPayload, kbnVersion: string, callAsInternalUser: APICaller, - xpackMainPlugin: any + isSecurityDisabled: boolean ): string[]; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.js b/x-pack/plugins/ml/server/models/job_validation/job_validation.js similarity index 94% rename from x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.js rename to x-pack/plugins/ml/server/models/job_validation/job_validation.js index ab1fbb39ee7062..d453c9add97d11 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.js +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.js @@ -8,11 +8,14 @@ import { i18n } from '@kbn/i18n'; import Boom from 'boom'; import { fieldsServiceProvider } from '../fields_service'; -import { renderTemplate } from '../../../common/util/string_utils'; +import { renderTemplate } from '../../../../../legacy/plugins/ml/common/util/string_utils'; import { getMessages } from './messages'; -import { VALIDATION_STATUS } from '../../../common/constants/validation'; +import { VALIDATION_STATUS } from '../../../../../legacy/plugins/ml/common/constants/validation'; -import { basicJobValidation, uniqWithIsEqual } from '../../../common/util/job_utils'; +import { + basicJobValidation, + uniqWithIsEqual, +} from '../../../../../legacy/plugins/ml/common/util/job_utils'; import { validateBucketSpan } from './validate_bucket_span'; import { validateCardinality } from './validate_cardinality'; import { validateInfluencers } from './validate_influencers'; @@ -24,7 +27,7 @@ export async function validateJob( payload, kbnVersion = 'current', callAsInternalUser, - xpackMainPlugin + isSecurityDisabled ) { const messages = getMessages(); @@ -112,7 +115,7 @@ export async function validateJob( job, duration, callAsInternalUser, - xpackMainPlugin + isSecurityDisabled )) ); validationMessages.push(...(await validateTimeRange(callWithRequest, job, duration))); diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js b/x-pack/plugins/ml/server/models/job_validation/messages.js similarity index 99% rename from x-pack/legacy/plugins/ml/server/models/job_validation/messages.js rename to x-pack/plugins/ml/server/models/job_validation/messages.js index 2c0c218bf86b51..33931f03facc3a 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js +++ b/x-pack/plugins/ml/server/models/job_validation/messages.js @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { JOB_ID_MAX_LENGTH } from '../../../common/constants/validation'; +import { JOB_ID_MAX_LENGTH } from '../../../../../legacy/plugins/ml/common/constants/validation'; let messages; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js similarity index 93% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_bucket_span.js rename to x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js index 2914f086c1a83e..9e96e2219fb0f5 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_bucket_span.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js @@ -5,9 +5,9 @@ */ import { estimateBucketSpanFactory } from '../../models/bucket_span_estimator'; -import { mlFunctionToESAggregation } from '../../../common/util/job_utils'; -import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../common/constants/validation'; -import { parseInterval } from '../../../common/util/parse_interval'; +import { mlFunctionToESAggregation } from '../../../../../legacy/plugins/ml/common/util/job_utils'; +import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../../../legacy/plugins/ml/common/constants/validation'; +import { parseInterval } from '../../../../../legacy/plugins/ml/common/util/parse_interval'; import { validateJobObject } from './validate_job_object'; @@ -51,7 +51,7 @@ export async function validateBucketSpan( job, duration, callAsInternalUser, - xpackMainPlugin + isSecurityDisabled ) { validateJobObject(job); @@ -124,7 +124,7 @@ export async function validateBucketSpan( estimateBucketSpanFactory( callWithRequest, callAsInternalUser, - xpackMainPlugin + isSecurityDisabled )(data) .then(resolve) // this catch gets triggered when the estimation code runs without error diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.d.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts similarity index 78% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.d.ts rename to x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts index dc109055337889..d3930ecf44c8d5 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.d.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts @@ -5,7 +5,10 @@ */ import { APICaller } from 'src/core/server'; -import { Job, Datafeed } from '../../../public/application/jobs/new_job/common/job_creator/configs'; +import { + Job, + Datafeed, +} from '../../../../../legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs'; interface ValidateCardinalityConfig extends Job { datafeed_config?: Datafeed; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.js b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.js rename to x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_influencers.js b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_influencers.js rename to x-pack/plugins/ml/server/models/job_validation/validate_influencers.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_job_object.js b/x-pack/plugins/ml/server/models/job_validation/validate_job_object.js similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_job_object.js rename to x-pack/plugins/ml/server/models/job_validation/validate_job_object.js diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_model_memory_limit.js b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.js similarity index 98% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_model_memory_limit.js rename to x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.js index 733ed9c3c22c67..354a3124a534f7 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_model_memory_limit.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.js @@ -7,7 +7,7 @@ import numeral from '@elastic/numeral'; import { validateJobObject } from './validate_job_object'; import { calculateModelMemoryLimitProvider } from '../../models/calculate_model_memory_limit'; -import { ALLOWED_DATA_UNITS } from '../../../common/constants/validation'; +import { ALLOWED_DATA_UNITS } from '../../../../../legacy/plugins/ml/common/constants/validation'; // The minimum value the backend expects is 1MByte const MODEL_MEMORY_LIMIT_MINIMUM_BYTES = 1048576; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_time_range.js b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.js similarity index 93% rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_time_range.js rename to x-pack/plugins/ml/server/models/job_validation/validate_time_range.js index df14d372664961..e6a92b45649b0d 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_time_range.js +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.js @@ -6,8 +6,8 @@ import _ from 'lodash'; -import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/server'; -import { parseInterval } from '../../../common/util/parse_interval'; +import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/server'; +import { parseInterval } from '../../../../../legacy/plugins/ml/common/util/parse_interval'; import { validateJobObject } from './validate_job_object'; const BUCKET_SPAN_COMPARE_FACTOR = 25; diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts similarity index 89% rename from x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts rename to x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts index 2bd19985c85189..f2d74fb9152996 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts +++ b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AnomalyRecordDoc } from '../../../common/types/anomalies'; +import { AnomalyRecordDoc } from '../../../../../legacy/plugins/ml/common/types/anomalies'; export interface AnomaliesTableRecord { time: number; diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.js b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js similarity index 99% rename from x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.js rename to x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js index 4934a0ba07081e..fc4280c74994d9 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.js +++ b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js @@ -12,7 +12,7 @@ import { getEntityFieldValue, showActualForFunction, showTypicalForFunction, -} from '../../../common/util/anomaly_utils'; +} from '../../../../../legacy/plugins/ml/common/util/anomaly_utils'; // Builds the items for display in the anomalies table from the supplied list of anomaly records. // Provide the timezone to use for aggregating anomalies (by day or hour) as set in the diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts similarity index 95% rename from x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts rename to x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts index 99eeaacc8de9cd..5d536059cb0a27 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts +++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -5,8 +5,8 @@ */ import Boom from 'boom'; -import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; -import { callWithRequestType } from '../../../common/types/kibana'; +import { ML_RESULTS_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; +import { callWithRequestType } from '../../../../../legacy/plugins/ml/common/types/kibana'; import { CriteriaField } from './results_service'; const PARTITION_FIELDS = ['partition_field', 'over_field', 'by_field'] as const; diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/index.ts b/x-pack/plugins/ml/server/models/results_service/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/models/results_service/index.ts rename to x-pack/plugins/ml/server/models/results_service/index.ts diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts similarity index 97% rename from x-pack/legacy/plugins/ml/server/models/results_service/results_service.ts rename to x-pack/plugins/ml/server/models/results_service/results_service.ts index 555a58fbb53335..324cbb91ca8c10 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -9,10 +9,10 @@ import moment from 'moment'; import { SearchResponse } from 'elasticsearch'; import { RequestHandlerContext } from 'kibana/server'; import { buildAnomalyTableItems, AnomaliesTableRecord } from './build_anomaly_table_items'; -import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; -import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; +import { ML_RESULTS_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns'; +import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../../../legacy/plugins/ml/common/constants/search'; import { getPartitionFieldsValuesFactory } from './get_partition_fields_values'; -import { AnomalyRecordDoc } from '../../../common/types/anomalies'; +import { AnomalyRecordDoc } from '../../../../../legacy/plugins/ml/common/types/anomalies'; // Service for carrying out Elasticsearch queries to obtain data for the // ML Results dashboards. diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts new file mode 100644 index 00000000000000..b5adf1fedec791 --- /dev/null +++ b/x-pack/plugins/ml/server/plugin.ts @@ -0,0 +1,168 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { CoreSetup, IScopedClusterClient, Logger, PluginInitializerContext } from 'src/core/server'; +import { LicenseCheckResult, PluginsSetup, RouteInitialization } from './types'; +import { PLUGIN_ID } from '../../../legacy/plugins/ml/common/constants/app'; +import { VALID_FULL_LICENSE_MODES } from '../../../legacy/plugins/ml/common/constants/license'; + +// @ts-ignore: could not find declaration file for module +import { elasticsearchJsPlugin } from './client/elasticsearch_ml'; +import { makeMlUsageCollector } from './lib/ml_telemetry'; +import { initMlServerLog } from './client/log'; +import { addLinksToSampleDatasets } from './lib/sample_data_sets'; + +import { annotationRoutes } from './routes/annotations'; +import { calendars } from './routes/calendars'; +import { dataFeedRoutes } from './routes/datafeeds'; +import { dataFrameAnalyticsRoutes } from './routes/data_frame_analytics'; +import { dataRecognizer } from './routes/modules'; +import { dataVisualizerRoutes } from './routes/data_visualizer'; +import { fieldsService } from './routes/fields_service'; +import { fileDataVisualizerRoutes } from './routes/file_data_visualizer'; +import { filtersRoutes } from './routes/filters'; +import { indicesRoutes } from './routes/indices'; +import { jobAuditMessagesRoutes } from './routes/job_audit_messages'; +import { jobRoutes } from './routes/anomaly_detectors'; +import { jobServiceRoutes } from './routes/job_service'; +import { jobValidationRoutes } from './routes/job_validation'; +import { notificationRoutes } from './routes/notification_settings'; +import { resultsServiceRoutes } from './routes/results_service'; +import { systemRoutes } from './routes/system'; + +declare module 'kibana/server' { + interface RequestHandlerContext { + ml?: { + mlClient: IScopedClusterClient; + }; + } +} + +export class MlServerPlugin { + private readonly pluginId: string = PLUGIN_ID; + private log: Logger; + private version: string; + + private licenseCheckResults: LicenseCheckResult = { + isAvailable: false, + isActive: false, + isEnabled: false, + isSecurityDisabled: false, + }; + + constructor(ctx: PluginInitializerContext) { + this.log = ctx.logger.get(); + this.version = ctx.env.packageInfo.branch; + } + + public setup(coreSetup: CoreSetup, plugins: PluginsSetup) { + let sampleLinksInitialized = false; + + plugins.features.registerFeature({ + id: PLUGIN_ID, + name: i18n.translate('xpack.ml.featureRegistry.mlFeatureName', { + defaultMessage: 'Machine Learning', + }), + icon: 'machineLearningApp', + navLinkId: PLUGIN_ID, + app: [PLUGIN_ID, 'kibana'], + catalogue: [PLUGIN_ID], + privileges: {}, + reserved: { + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + description: i18n.translate('xpack.ml.feature.reserved.description', { + defaultMessage: + 'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.', + }), + }, + }); + + // Can access via router's handler function 'context' parameter - context.ml.mlClient + const mlClient = coreSetup.elasticsearch.createClient(PLUGIN_ID, { + plugins: [elasticsearchJsPlugin], + }); + + coreSetup.http.registerRouteHandlerContext(PLUGIN_ID, (context, request) => { + return { + mlClient: mlClient.asScoped(request), + }; + }); + + const routeInit: RouteInitialization = { + router: coreSetup.http.createRouter(), + getLicenseCheckResults: () => this.licenseCheckResults, + }; + + annotationRoutes(routeInit, plugins.security); + calendars(routeInit); + dataFeedRoutes(routeInit); + dataFrameAnalyticsRoutes(routeInit); + dataRecognizer(routeInit); + dataVisualizerRoutes(routeInit); + fieldsService(routeInit); + fileDataVisualizerRoutes(routeInit); + filtersRoutes(routeInit); + indicesRoutes(routeInit); + jobAuditMessagesRoutes(routeInit); + jobRoutes(routeInit); + jobServiceRoutes(routeInit); + notificationRoutes(routeInit); + resultsServiceRoutes(routeInit); + jobValidationRoutes(routeInit, this.version); + systemRoutes(routeInit, { + spacesPlugin: plugins.spaces, + cloud: plugins.cloud, + }); + initMlServerLog({ log: this.log }); + coreSetup.getStartServices().then(([core]) => { + makeMlUsageCollector(plugins.usageCollection, core.savedObjects); + }); + + plugins.licensing.license$.subscribe(async license => { + const { isEnabled: securityIsEnabled } = license.getFeature('security'); + // @ts-ignore isAvailable is not read + const { isAvailable, isEnabled } = license.getFeature(this.pluginId); + + this.licenseCheckResults = { + isActive: license.isActive, + // This `isAvailable` check for the ml plugin returns false for a basic license + // ML should be available on basic with reduced functionality (only file data visualizer) + // TODO: This will need to be updated in the second step of this cutover to NP. + isAvailable: isEnabled, + isEnabled, + isSecurityDisabled: securityIsEnabled === false, + type: license.type, + }; + + if (sampleLinksInitialized === false) { + sampleLinksInitialized = true; + // Add links to the Kibana sample data sets if ml is enabled + // and license is trial or platinum. + if (isEnabled === true && plugins.home) { + if ( + this.licenseCheckResults.type && + VALID_FULL_LICENSE_MODES.includes(this.licenseCheckResults.type) + ) { + addLinksToSampleDatasets({ + addAppLinksToSampleDataset: plugins.home.sampleData.addAppLinksToSampleDataset, + }); + } + } + } + }); + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/legacy/plugins/ml/server/routes/README.md b/x-pack/plugins/ml/server/routes/README.md similarity index 100% rename from x-pack/legacy/plugins/ml/server/routes/README.md rename to x-pack/plugins/ml/server/routes/README.md diff --git a/x-pack/legacy/plugins/ml/server/routes/annotations.ts b/x-pack/plugins/ml/server/routes/annotations.ts similarity index 83% rename from x-pack/legacy/plugins/ml/server/routes/annotations.ts rename to x-pack/plugins/ml/server/routes/annotations.ts index 20f52b4b051c4c..bcc0238c366a3d 100644 --- a/x-pack/legacy/plugins/ml/server/routes/annotations.ts +++ b/x-pack/plugins/ml/server/routes/annotations.ts @@ -9,18 +9,19 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; +import { SecurityPluginSetup } from '../../../security/server'; import { isAnnotationsFeatureAvailable } from '../lib/check_annotations'; import { annotationServiceProvider } from '../models/annotation_service'; import { wrapError } from '../client/error_wrapper'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization } from '../types'; import { deleteAnnotationSchema, getAnnotationsSchema, indexAnnotationSchema, -} from '../new_platform/annotations_schema'; +} from './schemas/annotations_schema'; -import { ANNOTATION_USER_UNKNOWN } from '../../common/constants/annotations'; +import { ANNOTATION_USER_UNKNOWN } from '../../../../legacy/plugins/ml/common/constants/annotations'; function getAnnotationsFeatureUnavailableErrorMessage() { return Boom.badRequest( @@ -34,7 +35,10 @@ function getAnnotationsFeatureUnavailableErrorMessage() { /** * Routes for annotations */ -export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: RouteInitialization) { +export function annotationRoutes( + { router, getLicenseCheckResults }: RouteInitialization, + securityPlugin: SecurityPluginSetup +) { /** * @apiGroup Annotations * @@ -57,7 +61,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro body: schema.object(getAnnotationsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { getAnnotations } = annotationServiceProvider(context); const resp = await getAnnotations(request.body); @@ -88,7 +92,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro body: schema.object(indexAnnotationSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable( context.ml!.mlClient.callAsCurrentUser @@ -99,6 +103,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro const { indexAnnotation } = annotationServiceProvider(context); const user = securityPlugin.authc.getCurrentUser(request) || {}; + // @ts-ignore username doesn't exist on {} const resp = await indexAnnotation(request.body, user.username || ANNOTATION_USER_UNKNOWN); return response.ok({ @@ -126,7 +131,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro params: schema.object(deleteAnnotationSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable( context.ml!.mlClient.callAsCurrentUser diff --git a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts similarity index 89% rename from x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts rename to x-pack/plugins/ml/server/routes/anomaly_detectors.ts index 99dbdec9e945bd..7bf2fb7bc6903d 100644 --- a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -6,17 +6,17 @@ import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization } from '../types'; import { anomalyDetectionJobSchema, anomalyDetectionUpdateJobSchema, -} from '../new_platform/anomaly_detectors_schema'; +} from './schemas/anomaly_detectors_schema'; /** * Routes for the anomaly detectors */ -export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function jobRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup AnomalyDetectors * @@ -32,7 +32,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { path: '/api/ml/anomaly_detectors', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobs'); return response.ok({ @@ -62,7 +62,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobs', { jobId }); @@ -90,7 +90,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { path: '/api/ml/anomaly_detectors/_stats', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobStats'); return response.ok({ @@ -120,7 +120,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobStats', { jobId }); @@ -152,7 +152,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { body: schema.object({ ...anomalyDetectionJobSchema }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.addJob', { @@ -187,7 +187,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { body: schema.object({ ...anomalyDetectionUpdateJobSchema }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.updateJob', { @@ -221,7 +221,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.openJob', { @@ -254,7 +254,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const options: { jobId: string; force?: boolean } = { jobId: request.params.jobId, @@ -291,7 +291,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const options: { jobId: string; force?: boolean } = { jobId: request.params.jobId, @@ -326,7 +326,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { body: schema.any(), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.validateDetector', { body: request.body, @@ -359,7 +359,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { body: schema.object({ duration: schema.any() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const jobId = request.params.jobId; const duration = request.body.duration; @@ -407,7 +407,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.records', { jobId: request.params.jobId, @@ -456,7 +456,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.buckets', { jobId: request.params.jobId, @@ -499,7 +499,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.overallBuckets', { jobId: request.params.jobId, @@ -537,7 +537,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const options = { jobId: request.params.jobId, diff --git a/x-pack/legacy/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json similarity index 100% rename from x-pack/legacy/plugins/ml/server/routes/apidoc.json rename to x-pack/plugins/ml/server/routes/apidoc.json diff --git a/x-pack/legacy/plugins/ml/server/routes/calendars.ts b/x-pack/plugins/ml/server/routes/calendars.ts similarity index 84% rename from x-pack/legacy/plugins/ml/server/routes/calendars.ts rename to x-pack/plugins/ml/server/routes/calendars.ts index 8e4e1c4c14751d..ae494d3578890b 100644 --- a/x-pack/legacy/plugins/ml/server/routes/calendars.ts +++ b/x-pack/plugins/ml/server/routes/calendars.ts @@ -6,10 +6,10 @@ import { RequestHandlerContext } from 'src/core/server'; import { schema } from '@kbn/config-schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; -import { calendarSchema } from '../new_platform/calendars_schema'; +import { RouteInitialization } from '../types'; +import { calendarSchema } from './schemas/calendars_schema'; import { CalendarManager, Calendar, FormCalendar } from '../models/calendar'; function getAllCalendars(context: RequestHandlerContext) { @@ -42,13 +42,13 @@ function getCalendarsByIds(context: RequestHandlerContext, calendarIds: string) return cal.getCalendarsByIds(calendarIds); } -export function calendars({ xpackMainPlugin, router }: RouteInitialization) { +export function calendars({ router, getLicenseCheckResults }: RouteInitialization) { router.get( { path: '/api/ml/calendars', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getAllCalendars(context); @@ -68,7 +68,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) { params: schema.object({ calendarIds: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { let returnValue; try { const calendarIds = request.params.calendarIds.split(','); @@ -95,7 +95,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) { body: schema.object({ ...calendarSchema }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const body = request.body; const resp = await newCalendar(context, body); @@ -117,7 +117,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) { body: schema.object({ ...calendarSchema }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { calendarId } = request.params; const body = request.body; @@ -139,7 +139,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) { params: schema.object({ calendarId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { calendarId } = request.params; const resp = await deleteCalendar(context, calendarId); diff --git a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts similarity index 89% rename from x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts rename to x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 6541fa541a59ff..0a93320c05eb5b 100644 --- a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -7,18 +7,18 @@ import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization } from '../types'; import { dataAnalyticsJobConfigSchema, dataAnalyticsEvaluateSchema, dataAnalyticsExplainSchema, -} from '../new_platform/data_analytics_schema'; +} from './schemas/data_analytics_schema'; /** * Routes for the data frame analytics */ -export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function dataFrameAnalyticsRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup DataFrameAnalytics * @@ -36,7 +36,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti params: schema.object({ analyticsId: schema.maybe(schema.string()) }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics'); return response.ok({ @@ -64,7 +64,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti params: schema.object({ analyticsId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { analyticsId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics', { @@ -91,7 +91,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti path: '/api/ml/data_frame/analytics/_stats', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser( 'ml.getDataFrameAnalyticsStats' @@ -121,7 +121,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti params: schema.object({ analyticsId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { analyticsId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser( @@ -159,7 +159,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti body: schema.object(dataAnalyticsJobConfigSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { analyticsId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser( @@ -192,7 +192,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti body: schema.object({ ...dataAnalyticsEvaluateSchema }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser( 'ml.evaluateDataFrameAnalytics', @@ -232,7 +232,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti body: schema.object({ ...dataAnalyticsExplainSchema }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser( 'ml.explainDataFrameAnalytics', @@ -267,7 +267,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { analyticsId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser( @@ -303,7 +303,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { analyticsId } = request.params; const results = await context.ml!.mlClient.callAsCurrentUser('ml.startDataFrameAnalytics', { @@ -337,7 +337,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const options: { analyticsId: string; force?: boolean | undefined } = { analyticsId: request.params.analyticsId, @@ -377,7 +377,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti params: schema.object({ analyticsId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { analyticsId } = request.params; const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider( diff --git a/x-pack/legacy/plugins/ml/server/routes/data_visualizer.ts b/x-pack/plugins/ml/server/routes/data_visualizer.ts similarity index 89% rename from x-pack/legacy/plugins/ml/server/routes/data_visualizer.ts rename to x-pack/plugins/ml/server/routes/data_visualizer.ts index df7e4b70108777..e4d068784def1b 100644 --- a/x-pack/legacy/plugins/ml/server/routes/data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/data_visualizer.ts @@ -11,9 +11,9 @@ import { Field } from '../models/data_visualizer/data_visualizer'; import { dataVisualizerFieldStatsSchema, dataVisualizerOverallStatsSchema, -} from '../new_platform/data_visualizer_schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +} from './schemas/data_visualizer_schema'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization } from '../types'; function getOverallStats( context: RequestHandlerContext, @@ -68,7 +68,7 @@ function getStatsForFields( /** * Routes for the index data visualizer. */ -export function dataVisualizerRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function dataVisualizerRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup DataVisualizer * @@ -83,7 +83,7 @@ export function dataVisualizerRoutes({ xpackMainPlugin, router }: RouteInitializ path: '/api/ml/data_visualizer/get_field_stats/{indexPatternTitle}', validate: dataVisualizerFieldStatsSchema, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { params: { indexPatternTitle }, @@ -135,7 +135,7 @@ export function dataVisualizerRoutes({ xpackMainPlugin, router }: RouteInitializ path: '/api/ml/data_visualizer/get_overall_stats/{indexPatternTitle}', validate: dataVisualizerOverallStatsSchema, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { params: { indexPatternTitle }, diff --git a/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts b/x-pack/plugins/ml/server/routes/datafeeds.ts similarity index 86% rename from x-pack/legacy/plugins/ml/server/routes/datafeeds.ts rename to x-pack/plugins/ml/server/routes/datafeeds.ts index 9335403616cf7d..e3bce4c1328e4e 100644 --- a/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts +++ b/x-pack/plugins/ml/server/routes/datafeeds.ts @@ -5,15 +5,15 @@ */ import { schema } from '@kbn/config-schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; -import { startDatafeedSchema, datafeedConfigSchema } from '../new_platform/datafeeds_schema'; +import { RouteInitialization } from '../types'; +import { startDatafeedSchema, datafeedConfigSchema } from './schemas/datafeeds_schema'; /** * Routes for datafeed service */ -export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function dataFeedRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup DatafeedService * @@ -26,7 +26,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) path: '/api/ml/datafeeds', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeeds'); @@ -53,7 +53,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) params: schema.object({ datafeedId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeeds', { datafeedId }); @@ -79,7 +79,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) path: '/api/ml/datafeeds/_stats', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedStats'); @@ -106,7 +106,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) params: schema.object({ datafeedId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedStats', { @@ -137,7 +137,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) body: datafeedConfigSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; const resp = await context.ml!.mlClient.callAsCurrentUser('ml.addDatafeed', { @@ -169,7 +169,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) body: datafeedConfigSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; const resp = await context.ml!.mlClient.callAsCurrentUser('ml.updateDatafeed', { @@ -201,7 +201,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) query: schema.maybe(schema.object({ force: schema.maybe(schema.any()) })), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const options: { datafeedId: string; force?: boolean } = { datafeedId: request.params.jobId, @@ -237,7 +237,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) body: startDatafeedSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; const { start, end } = request.body; @@ -271,7 +271,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) params: schema.object({ datafeedId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; @@ -302,7 +302,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) params: schema.object({ datafeedId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const datafeedId = request.params.datafeedId; const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedPreview', { diff --git a/x-pack/legacy/plugins/ml/server/routes/fields_service.ts b/x-pack/plugins/ml/server/routes/fields_service.ts similarity index 84% rename from x-pack/legacy/plugins/ml/server/routes/fields_service.ts rename to x-pack/plugins/ml/server/routes/fields_service.ts index 4827adf23d7b46..bc092190c2c620 100644 --- a/x-pack/legacy/plugins/ml/server/routes/fields_service.ts +++ b/x-pack/plugins/ml/server/routes/fields_service.ts @@ -5,13 +5,13 @@ */ import { RequestHandlerContext } from 'src/core/server'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; +import { RouteInitialization } from '../types'; import { getCardinalityOfFieldsSchema, getTimeFieldRangeSchema, -} from '../new_platform/fields_service_schema'; +} from './schemas/fields_service_schema'; import { fieldsServiceProvider } from '../models/fields_service'; function getCardinalityOfFields(context: RequestHandlerContext, payload: any) { @@ -29,7 +29,7 @@ function getTimeFieldRange(context: RequestHandlerContext, payload: any) { /** * Routes for fields service */ -export function fieldsService({ xpackMainPlugin, router }: RouteInitialization) { +export function fieldsService({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup FieldsService * @@ -44,7 +44,7 @@ export function fieldsService({ xpackMainPlugin, router }: RouteInitialization) body: getCardinalityOfFieldsSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getCardinalityOfFields(context, request.body); @@ -71,7 +71,7 @@ export function fieldsService({ xpackMainPlugin, router }: RouteInitialization) body: getTimeFieldRangeSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getTimeFieldRange(context, request.body); diff --git a/x-pack/legacy/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts similarity index 87% rename from x-pack/legacy/plugins/ml/server/routes/file_data_visualizer.ts rename to x-pack/plugins/ml/server/routes/file_data_visualizer.ts index d5a992c9332930..1d724a8843350d 100644 --- a/x-pack/legacy/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; import { RequestHandlerContext } from 'kibana/server'; -import { MAX_BYTES } from '../../common/constants/file_datavisualizer'; +import { MAX_BYTES } from '../../../../legacy/plugins/ml/common/constants/file_datavisualizer'; import { wrapError } from '../client/error_wrapper'; import { InputOverrides, @@ -18,8 +18,8 @@ import { Mappings, } from '../models/file_data_visualizer'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization } from '../types'; import { incrementFileDataVisualizerIndexCreationCount } from '../lib/ml_telemetry'; function analyzeFiles(context: RequestHandlerContext, data: InputData, overrides: InputOverrides) { @@ -43,12 +43,7 @@ function importData( /** * Routes for the file data visualizer. */ -export function fileDataVisualizerRoutes({ - router, - xpackMainPlugin, - savedObjects, - elasticsearchPlugin, -}: RouteInitialization) { +export function fileDataVisualizerRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup FileDataVisualizer * @@ -87,7 +82,7 @@ export function fileDataVisualizerRoutes({ }, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const result = await analyzeFiles(context, request.body, request.query); return response.ok({ body: result }); @@ -129,7 +124,7 @@ export function fileDataVisualizerRoutes({ }, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { id } = request.query; const { index, data, settings, mappings, ingestPipeline } = request.body; @@ -138,7 +133,8 @@ export function fileDataVisualizerRoutes({ // follow-up import calls to just add additional data will include the `id` of the created // index, we'll ignore those and don't increment the counter. if (id === undefined) { - await incrementFileDataVisualizerIndexCreationCount(savedObjects!); + // @ts-ignore + await incrementFileDataVisualizerIndexCreationCount(context.core.savedObjects.client); } const result = await importData( diff --git a/x-pack/legacy/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts similarity index 87% rename from x-pack/legacy/plugins/ml/server/routes/filters.ts rename to x-pack/plugins/ml/server/routes/filters.ts index a06f8d4f8b727b..d5530668b26062 100644 --- a/x-pack/legacy/plugins/ml/server/routes/filters.ts +++ b/x-pack/plugins/ml/server/routes/filters.ts @@ -6,10 +6,10 @@ import { RequestHandlerContext } from 'src/core/server'; import { schema } from '@kbn/config-schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; -import { createFilterSchema, updateFilterSchema } from '../new_platform/filters_schema'; +import { RouteInitialization } from '../types'; +import { createFilterSchema, updateFilterSchema } from './schemas/filters_schema'; import { FilterManager, FormFilter } from '../models/filter'; // TODO - add function for returning a list of just the filter IDs. @@ -44,7 +44,7 @@ function deleteFilter(context: RequestHandlerContext, filterId: string) { return mgr.deleteFilter(filterId); } -export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function filtersRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup Filters * @@ -60,7 +60,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) path: '/api/ml/filters', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getAllFilters(context); @@ -90,7 +90,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) params: schema.object({ filterId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getFilter(context, request.params.filterId); return response.ok({ @@ -119,7 +119,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) body: schema.object(createFilterSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const body = request.body; const resp = await newFilter(context, body); @@ -151,7 +151,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) body: schema.object(updateFilterSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { filterId } = request.params; const body = request.body; @@ -182,7 +182,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) params: schema.object({ filterId: schema.string() }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { filterId } = request.params; const resp = await deleteFilter(context, filterId); @@ -212,7 +212,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) path: '/api/ml/filters/_stats', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getAllFilterStats(context); diff --git a/x-pack/legacy/plugins/ml/server/routes/indices.ts b/x-pack/plugins/ml/server/routes/indices.ts similarity index 80% rename from x-pack/legacy/plugins/ml/server/routes/indices.ts rename to x-pack/plugins/ml/server/routes/indices.ts index 0ee15f1321e9c2..e01a7a0cbad287 100644 --- a/x-pack/legacy/plugins/ml/server/routes/indices.ts +++ b/x-pack/plugins/ml/server/routes/indices.ts @@ -6,13 +6,13 @@ import { schema } from '@kbn/config-schema'; import { wrapError } from '../client/error_wrapper'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization } from '../types'; /** * Indices routes. */ -export function indicesRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function indicesRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup Indices * @@ -30,7 +30,7 @@ export function indicesRoutes({ xpackMainPlugin, router }: RouteInitialization) }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { body: { index, fields: requestFields }, diff --git a/x-pack/legacy/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts similarity index 84% rename from x-pack/legacy/plugins/ml/server/routes/job_audit_messages.ts rename to x-pack/plugins/ml/server/routes/job_audit_messages.ts index 76986b935b993a..38df28e17ec0d6 100644 --- a/x-pack/legacy/plugins/ml/server/routes/job_audit_messages.ts +++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts @@ -5,15 +5,15 @@ */ import { schema } from '@kbn/config-schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; +import { RouteInitialization } from '../types'; import { jobAuditMessagesProvider } from '../models/job_audit_messages'; /** * Routes for job audit message routes */ -export function jobAuditMessagesRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function jobAuditMessagesRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup JobAuditMessages * @@ -29,7 +29,7 @@ export function jobAuditMessagesRoutes({ xpackMainPlugin, router }: RouteInitial query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { getJobAuditMessages } = jobAuditMessagesProvider( context.ml!.mlClient.callAsCurrentUser @@ -62,7 +62,7 @@ export function jobAuditMessagesRoutes({ xpackMainPlugin, router }: RouteInitial query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { getJobAuditMessages } = jobAuditMessagesProvider( context.ml!.mlClient.callAsCurrentUser diff --git a/x-pack/legacy/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts similarity index 88% rename from x-pack/legacy/plugins/ml/server/routes/job_service.ts rename to x-pack/plugins/ml/server/routes/job_service.ts index 5ddbd4cdfd5a5d..e15888088d3a13 100644 --- a/x-pack/legacy/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -7,10 +7,9 @@ import Boom from 'boom'; import { schema } from '@kbn/config-schema'; import { IScopedClusterClient } from 'src/core/server'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; -import { isSecurityDisabled } from '../lib/security_utils'; +import { RouteInitialization } from '../types'; import { categorizationFieldExamplesSchema, chartSchema, @@ -21,7 +20,7 @@ import { lookBackProgressSchema, topCategoriesSchema, updateGroupsSchema, -} from '../new_platform/job_service_schema'; +} from './schemas/job_service_schema'; // @ts-ignore no declaration module import { jobServiceProvider } from '../models/job_service'; import { categorizationExamplesProvider } from '../models/job_service/new_job'; @@ -29,11 +28,12 @@ import { categorizationExamplesProvider } from '../models/job_service/new_job'; /** * Routes for job service */ -export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function jobServiceRoutes({ router, getLicenseCheckResults }: RouteInitialization) { async function hasPermissionToCreateJobs( callAsCurrentUser: IScopedClusterClient['callAsCurrentUser'] ) { - if (isSecurityDisabled(xpackMainPlugin) === true) { + const { isSecurityDisabled } = getLicenseCheckResults(); + if (isSecurityDisabled === true) { return true; } @@ -63,7 +63,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(forceStartDatafeedSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { forceStartDatafeeds } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { datafeedIds, start, end } = request.body; @@ -92,7 +92,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(datafeedIdsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { stopDatafeeds } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { datafeedIds } = request.body; @@ -121,7 +121,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(jobIdsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { deleteJobs } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobIds } = request.body; @@ -150,7 +150,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(jobIdsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { closeJobs } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobIds } = request.body; @@ -179,7 +179,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(jobIdsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobsSummary } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobIds } = request.body; @@ -208,7 +208,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(jobsWithTimerangeSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobsWithTimerange } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { dateFormatTz } = request.body; @@ -237,7 +237,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(jobIdsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { createFullJobsList } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobIds } = request.body; @@ -264,7 +264,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio path: '/api/ml/jobs/groups', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { getAllGroups } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const resp = await getAllGroups(); @@ -292,7 +292,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(updateGroupsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { updateGroups } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobs } = request.body; @@ -319,7 +319,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio path: '/api/ml/jobs/deleting_jobs_tasks', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { deletingJobTasks } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const resp = await deletingJobTasks(); @@ -347,7 +347,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(jobIdsSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { jobsExist } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobIds } = request.body; @@ -377,7 +377,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio query: schema.maybe(schema.object({ rollup: schema.maybe(schema.string()) })), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { indexPattern } = request.params; const isRollup = request.query.rollup === 'true'; @@ -408,7 +408,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(chartSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { indexPatternTitle, @@ -461,7 +461,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(chartSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { indexPatternTitle, @@ -509,7 +509,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio path: '/api/ml/jobs/all_jobs_and_group_ids', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { getAllJobAndGroupIds } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const resp = await getAllJobAndGroupIds(); @@ -537,7 +537,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(lookBackProgressSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { getLookBackProgress } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobId, start, end } = request.body; @@ -566,7 +566,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(categorizationFieldExamplesSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { // due to the use of the _analyze endpoint which is called by the kibana user, // basic job creation privileges are required to use this endpoint @@ -625,7 +625,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio body: schema.object(topCategoriesSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { topCategories } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const { jobId, count } = request.body; diff --git a/x-pack/legacy/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts similarity index 85% rename from x-pack/legacy/plugins/ml/server/routes/job_validation.ts rename to x-pack/plugins/ml/server/routes/job_validation.ts index 64c9ccd27720a8..ae2e6885ba0f3b 100644 --- a/x-pack/legacy/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -7,15 +7,15 @@ import Boom from 'boom'; import { RequestHandlerContext } from 'src/core/server'; import { schema, TypeOf } from '@kbn/config-schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; +import { RouteInitialization } from '../types'; import { estimateBucketSpanSchema, modelMemoryLimitSchema, validateCardinalitySchema, validateJobSchema, -} from '../new_platform/job_validation_schema'; +} from './schemas/job_validation_schema'; import { estimateBucketSpanFactory } from '../models/bucket_span_estimator'; import { calculateModelMemoryLimitProvider } from '../models/calculate_model_memory_limit'; import { validateJob, validateCardinality } from '../models/job_validation'; @@ -25,7 +25,10 @@ type CalculateModelMemoryLimitPayload = TypeOf; /** * Routes for job validation */ -export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteInitialization) { +export function jobValidationRoutes( + { getLicenseCheckResults, router }: RouteInitialization, + version: string +) { function calculateModelMemoryLimit( context: RequestHandlerContext, payload: CalculateModelMemoryLimitPayload @@ -67,13 +70,13 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn body: estimateBucketSpanSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { let errorResp; const resp = await estimateBucketSpanFactory( context.ml!.mlClient.callAsCurrentUser, context.core.elasticsearch.adminClient.callAsInternalUser, - xpackMainPlugin + getLicenseCheckResults().isSecurityDisabled )(request.body) // this catch gets triggered when the estimation code runs without error // but isn't able to come up with a bucket span estimation. @@ -114,7 +117,7 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn body: modelMemoryLimitSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await calculateModelMemoryLimit(context, request.body); @@ -141,7 +144,7 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn body: schema.object(validateCardinalitySchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await validateCardinality( context.ml!.mlClient.callAsCurrentUser, @@ -171,16 +174,15 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn body: validateJobSchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { - // pkg.branch corresponds to the version used in documentation links. - const version = config.get('pkg.branch'); + // version corresponds to the version used in documentation links. const resp = await validateJob( context.ml!.mlClient.callAsCurrentUser, request.body, version, context.core.elasticsearch.adminClient.callAsInternalUser, - xpackMainPlugin + getLicenseCheckResults().isSecurityDisabled ); return response.ok({ diff --git a/x-pack/legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts b/x-pack/plugins/ml/server/routes/license_check_pre_routing_factory.ts similarity index 71% rename from x-pack/legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts rename to x-pack/plugins/ml/server/routes/license_check_pre_routing_factory.ts index cc77d2872fb909..a371af1abf2d13 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts +++ b/x-pack/plugins/ml/server/routes/license_check_pre_routing_factory.ts @@ -10,10 +10,10 @@ import { RequestHandler, RequestHandlerContext, } from 'src/core/server'; -import { PLUGIN_ID, MlXpackMainPlugin } from './plugin'; +import { LicenseCheckResult } from '../types'; export const licensePreRoutingFactory = ( - xpackMainPlugin: MlXpackMainPlugin, + getLicenseCheckResults: () => LicenseCheckResult, handler: RequestHandler ): RequestHandler => { // License checking and enable/disable logic @@ -22,14 +22,10 @@ export const licensePreRoutingFactory = ( request: KibanaRequest, response: KibanaResponseFactory ) { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN_ID).getLicenseCheckResults(); + const licenseCheckResults = getLicenseCheckResults(); if (!licenseCheckResults.isAvailable) { - return response.forbidden({ - body: { - message: licenseCheckResults.message, - }, - }); + return response.forbidden(); } return handler(ctx, request, response); diff --git a/x-pack/legacy/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts similarity index 88% rename from x-pack/legacy/plugins/ml/server/routes/modules.ts rename to x-pack/plugins/ml/server/routes/modules.ts index a40fb1c9149ca1..c9b005d4e43f92 100644 --- a/x-pack/legacy/plugins/ml/server/routes/modules.ts +++ b/x-pack/plugins/ml/server/routes/modules.ts @@ -6,12 +6,12 @@ import { schema } from '@kbn/config-schema'; import { RequestHandlerContext } from 'kibana/server'; -import { DatafeedOverride, JobOverride } from '../../common/types/modules'; +import { DatafeedOverride, JobOverride } from '../../../../legacy/plugins/ml/common/types/modules'; import { wrapError } from '../client/error_wrapper'; import { DataRecognizer } from '../models/data_recognizer'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { getModuleIdParamSchema, setupModuleBodySchema } from '../new_platform/modules'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { getModuleIdParamSchema, setupModuleBodySchema } from './schemas/modules'; +import { RouteInitialization } from '../types'; function recognize(context: RequestHandlerContext, indexPatternTitle: string) { const dr = new DataRecognizer(context); @@ -65,7 +65,7 @@ function dataRecognizerJobsExist(context: RequestHandlerContext, moduleId: strin /** * Recognizer routes. */ -export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization) { +export function dataRecognizer({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup DataRecognizer * @@ -84,7 +84,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization) }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { indexPatternTitle } = request.params; const results = await recognize(context, indexPatternTitle); @@ -114,7 +114,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization) }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { let { moduleId } = request.params; if (moduleId === '') { @@ -150,7 +150,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization) body: setupModuleBodySchema, }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { moduleId } = request.params; @@ -207,7 +207,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization) }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const { moduleId } = request.params; const result = await dataRecognizerJobsExist(context, moduleId); diff --git a/x-pack/legacy/plugins/ml/server/routes/notification_settings.ts b/x-pack/plugins/ml/server/routes/notification_settings.ts similarity index 75% rename from x-pack/legacy/plugins/ml/server/routes/notification_settings.ts rename to x-pack/plugins/ml/server/routes/notification_settings.ts index c65627543b21d8..b68d2441333f93 100644 --- a/x-pack/legacy/plugins/ml/server/routes/notification_settings.ts +++ b/x-pack/plugins/ml/server/routes/notification_settings.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; +import { RouteInitialization } from '../types'; /** * Routes for notification settings */ -export function notificationRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function notificationRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup NotificationSettings * @@ -24,7 +24,7 @@ export function notificationRoutes({ xpackMainPlugin, router }: RouteInitializat path: '/api/ml/notification_settings', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const params = { includeDefaults: true, diff --git a/x-pack/legacy/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts similarity index 88% rename from x-pack/legacy/plugins/ml/server/routes/results_service.ts rename to x-pack/plugins/ml/server/routes/results_service.ts index 5d107b2d978090..77c998acc9f27a 100644 --- a/x-pack/legacy/plugins/ml/server/routes/results_service.ts +++ b/x-pack/plugins/ml/server/routes/results_service.ts @@ -6,16 +6,16 @@ import { RequestHandlerContext } from 'src/core/server'; import { schema } from '@kbn/config-schema'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; import { wrapError } from '../client/error_wrapper'; -import { RouteInitialization } from '../new_platform/plugin'; +import { RouteInitialization } from '../types'; import { anomaliesTableDataSchema, categoryDefinitionSchema, categoryExamplesSchema, maxAnomalyScoreSchema, partitionFieldValuesSchema, -} from '../new_platform/results_service_schema'; +} from './schemas/results_service_schema'; import { resultsServiceProvider } from '../models/results_service'; function getAnomaliesTableData(context: RequestHandlerContext, payload: any) { @@ -74,7 +74,7 @@ function getPartitionFieldsValues(context: RequestHandlerContext, payload: any) /** * Routes for results service */ -export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitialization) { +export function resultsServiceRoutes({ router, getLicenseCheckResults }: RouteInitialization) { /** * @apiGroup ResultsService * @@ -89,7 +89,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ body: schema.object(anomaliesTableDataSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getAnomaliesTableData(context, request.body); @@ -116,7 +116,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ body: schema.object(categoryDefinitionSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getCategoryDefinition(context, request.body); @@ -143,7 +143,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ body: schema.object(maxAnomalyScoreSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getMaxAnomalyScore(context, request.body); @@ -170,7 +170,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ body: schema.object(categoryExamplesSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getCategoryExamples(context, request.body); @@ -197,7 +197,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ body: schema.object(partitionFieldValuesSchema), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const resp = await getPartitionFieldsValues(context, request.body); diff --git a/x-pack/legacy/plugins/ml/server/new_platform/annotations_schema.ts b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/annotations_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/anomaly_detectors_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/data_visualizer_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/fields_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/fields_service_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/filters_schema.ts b/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/filters_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/filters_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/job_service_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/modules.ts b/x-pack/plugins/ml/server/routes/schemas/modules.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/modules.ts rename to x-pack/plugins/ml/server/routes/schemas/modules.ts diff --git a/x-pack/legacy/plugins/ml/server/new_platform/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts similarity index 100% rename from x-pack/legacy/plugins/ml/server/new_platform/results_service_schema.ts rename to x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts diff --git a/x-pack/legacy/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts similarity index 88% rename from x-pack/legacy/plugins/ml/server/routes/system.ts rename to x-pack/plugins/ml/server/routes/system.ts index 5861b53d74875c..36a9ea1447f583 100644 --- a/x-pack/legacy/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -11,20 +11,17 @@ import { RequestHandlerContext } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { mlLog } from '../client/log'; import { privilegesProvider } from '../lib/check_privileges'; -import { isSecurityDisabled } from '../lib/security_utils'; import { spacesUtilsProvider } from '../lib/spaces_utils'; -import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; -import { RouteInitialization } from '../new_platform/plugin'; +import { licensePreRoutingFactory } from './license_check_pre_routing_factory'; +import { RouteInitialization, SystemRouteDeps } from '../types'; /** * System routes */ -export function systemRoutes({ - router, - xpackMainPlugin, - spacesPlugin, - cloud, -}: RouteInitialization) { +export function systemRoutes( + { getLicenseCheckResults, router }: RouteInitialization, + { spacesPlugin, cloud }: SystemRouteDeps +) { async function getNodeCount(context: RequestHandlerContext) { const filterPath = 'nodes.*.attributes'; const resp = await context.ml!.mlClient.callAsInternalUser('nodes.info', { @@ -59,7 +56,7 @@ export function systemRoutes({ body: schema.maybe(schema.any()), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { let upgradeInProgress = false; try { @@ -80,7 +77,7 @@ export function systemRoutes({ } } - if (isSecurityDisabled(xpackMainPlugin)) { + if (getLicenseCheckResults().isSecurityDisabled) { // if xpack.security.enabled has been explicitly set to false // return that security is disabled and don't call the privilegeCheck endpoint return response.ok({ @@ -119,7 +116,7 @@ export function systemRoutes({ }), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const ignoreSpaces = request.query && request.query.ignoreSpaces === 'true'; // if spaces is disabled force isMlEnabledInSpace to be true @@ -130,7 +127,7 @@ export function systemRoutes({ const { getPrivileges } = privilegesProvider( context.ml!.mlClient.callAsCurrentUser, - xpackMainPlugin, + getLicenseCheckResults(), isMlEnabledInSpace, ignoreSpaces ); @@ -155,11 +152,11 @@ export function systemRoutes({ path: '/api/ml/ml_node_count', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { // check for basic license first for consistency with other // security disabled checks - if (isSecurityDisabled(xpackMainPlugin)) { + if (getLicenseCheckResults().isSecurityDisabled) { return response.ok({ body: await getNodeCount(context), }); @@ -206,7 +203,7 @@ export function systemRoutes({ path: '/api/ml/info', validate: false, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { const info = await context.ml!.mlClient.callAsCurrentUser('ml.info'); const cloudId = cloud && cloud.cloudId; @@ -234,7 +231,7 @@ export function systemRoutes({ body: schema.maybe(schema.any()), }, }, - licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => { try { return response.ok({ body: await context.ml!.mlClient.callAsCurrentUser('search', request.body), diff --git a/x-pack/plugins/ml/server/types.ts b/x-pack/plugins/ml/server/types.ts new file mode 100644 index 00000000000000..550abadb3c06f3 --- /dev/null +++ b/x-pack/plugins/ml/server/types.ts @@ -0,0 +1,43 @@ +/* + * 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 { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { HomeServerPluginSetup } from 'src/plugins/home/server'; +import { IRouter } from 'src/core/server'; +import { CloudSetup } from '../../cloud/server'; +import { SecurityPluginSetup } from '../../security/server'; +import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { SpacesPluginSetup } from '../../spaces/server'; + +export interface LicenseCheckResult { + isAvailable: boolean; + isActive: boolean; + isEnabled: boolean; + isSecurityDisabled: boolean; + status?: string; + type?: string; +} + +export interface SystemRouteDeps { + cloud: CloudSetup; + spacesPlugin: SpacesPluginSetup; +} + +export interface PluginsSetup { + cloud: CloudSetup; + features: FeaturesPluginSetup; + home: HomeServerPluginSetup; + licensing: LicensingPluginSetup; + security: SecurityPluginSetup; + spaces: SpacesPluginSetup; + usageCollection: UsageCollectionSetup; +} + +export interface RouteInitialization { + router: IRouter; + getLicenseCheckResults: () => LicenseCheckResult; +} From 0cede6a705db65ef450426db3c584fcabab42780 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Thu, 27 Feb 2020 19:17:07 -0700 Subject: [PATCH 21/28] Prep agg types for new platform (#57064) --- packages/kbn-utility-types/README.md | 1 + packages/kbn-utility-types/index.ts | 2 +- .../actions/filters/brush_event.test.ts | 49 +- src/legacy/core_plugins/data/public/index.ts | 12 +- src/legacy/core_plugins/data/public/plugin.ts | 6 +- .../public/search/aggs/agg_config.test.ts | 497 ++++++ .../data/public/search/aggs/agg_config.ts | 61 +- .../public/search/aggs/agg_configs.test.ts | 503 ++++++ .../data/public/search/aggs/agg_configs.ts | 76 +- .../public/search/aggs/agg_params.test.ts | 2 - .../data/public/search/aggs/agg_type.test.ts | 16 +- .../data/public/search/aggs/agg_type.ts | 5 +- .../search/aggs/agg_types_registry.test.ts | 91 ++ .../public/search/aggs/agg_types_registry.ts | 68 + .../search/aggs/buckets/_bucket_agg_type.ts | 12 +- .../search/aggs/buckets/_interval_options.ts | 1 + .../create_filter/date_histogram.test.ts | 12 +- .../buckets/create_filter/date_range.test.ts | 7 +- .../buckets/create_filter/filters.test.ts | 13 +- .../buckets/create_filter/histogram.test.ts | 12 +- .../buckets/create_filter/ip_range.test.ts | 11 +- .../aggs/buckets/create_filter/range.test.ts | 12 +- .../aggs/buckets/create_filter/terms.test.ts | 11 +- .../search/aggs/buckets/date_histogram.ts | 8 +- .../search/aggs/buckets/date_range.test.ts | 25 +- .../public/search/aggs/buckets/date_range.ts | 12 +- .../data/public/search/aggs/buckets/filter.ts | 1 + .../public/search/aggs/buckets/filters.ts | 26 +- .../search/aggs/buckets/geo_hash.test.ts | 7 +- .../public/search/aggs/buckets/geo_tile.ts | 3 +- .../search/aggs/buckets/histogram.test.ts | 33 +- .../public/search/aggs/buckets/histogram.ts | 12 +- .../public/search/aggs/buckets/ip_range.ts | 12 +- .../buckets/migrate_include_exclude_format.ts | 4 +- .../public/search/aggs/buckets/range.test.ts | 12 +- .../aggs/buckets/significant_terms.test.ts | 9 +- .../public/search/aggs/buckets/terms.test.ts | 8 +- .../aggs/filter/agg_type_filters.test.ts | 5 +- .../search/aggs/filter/agg_type_filters.ts | 1 + .../search/aggs/filter/prop_filter.test.ts | 19 +- .../data/public/search/aggs/index.test.ts | 2 - .../data/public/search/aggs/index.ts | 9 +- .../public/search/aggs/metrics/bucket_avg.ts | 1 - .../public/search/aggs/metrics/bucket_max.ts | 1 - .../public/search/aggs/metrics/bucket_min.ts | 1 + .../public/search/aggs/metrics/cardinality.ts | 5 +- .../data/public/search/aggs/metrics/count.ts | 7 +- .../lib/get_response_agg_config_class.ts | 1 + .../metrics/lib/parent_pipeline_agg_helper.ts | 1 - .../lib/sibling_pipeline_agg_helper.ts | 1 - .../public/search/aggs/metrics/median.test.ts | 7 +- .../data/public/search/aggs/metrics/median.ts | 4 +- .../search/aggs/metrics/metric_agg_type.ts | 7 +- .../data/public/search/aggs/metrics/min.ts | 1 + .../aggs/metrics/parent_pipeline.test.ts | 18 +- .../aggs/metrics/percentile_ranks.test.ts | 8 +- .../search/aggs/metrics/percentile_ranks.ts | 7 +- .../search/aggs/metrics/percentiles.test.ts | 6 +- .../public/search/aggs/metrics/percentiles.ts | 4 - .../aggs/metrics/sibling_pipeline.test.ts | 22 +- .../search/aggs/metrics/std_deviation.test.ts | 6 +- .../search/aggs/metrics/top_hit.test.ts | 6 +- .../public/search/aggs/param_types/agg.ts | 4 +- .../public/search/aggs/param_types/base.ts | 4 +- .../search/aggs/param_types/field.test.ts | 2 - .../public/search/aggs/param_types/field.ts | 5 +- .../param_types/filter/field_filters.test.ts | 11 +- .../aggs/param_types/filter/field_filters.ts | 8 +- .../search/aggs/param_types/json.test.ts | 8 +- .../public/search/aggs/param_types/json.ts | 4 +- .../search/aggs/param_types/optioned.test.ts | 2 - .../search/aggs/param_types/optioned.ts | 6 +- .../search/aggs/param_types/string.test.ts | 8 +- .../public/search/aggs/param_types/string.ts | 4 +- .../public/search/aggs/test_helpers/index.ts} | 4 +- .../test_helpers/mock_agg_types_registry.ts | 57 + .../aggs/test_helpers/mock_data_services.ts | 54 + .../data/public/search/aggs/types.ts | 2 +- .../data/public/search/aggs/utils.test.tsx | 2 - .../data/public/search/aggs/utils.ts | 39 +- .../data/public/search/expressions/esaggs.ts | 4 +- .../data/public/search/expressions/utils.ts | 5 +- .../core_plugins/data/public/search/mocks.ts | 85 + .../data/public/search/search_service.ts | 60 +- .../data/public/search/tabify/buckets.test.ts | 2 - .../public/search/tabify/get_columns.test.ts | 22 +- .../search/tabify/response_writer.test.ts | 20 +- .../data/public/search/tabify/tabify.test.ts | 16 +- .../brush_event.test.mocks.ts => services.ts} | 13 +- .../components/sidebar/state/reducers.ts | 18 +- .../public/legacy_imports.ts | 2 +- .../public/table_vis_controller.test.ts | 4 +- .../visualizations/public/legacy_imports.ts | 2 +- .../public/np_ready/public/vis_impl.js | 6 +- src/legacy/ui/public/agg_types/index.ts | 9 +- .../ui/public/vis/__tests__/_agg_config.js | 485 ------ .../ui/public/vis/__tests__/_agg_configs.js | 420 ----- .../data/common/field_formats/mocks.ts | 49 + src/plugins/data/public/mocks.ts | 28 +- .../data/public/search/search_source/mocks.ts | 19 - .../editor_frame_service/service.test.tsx | 4 - .../__snapshots__/zeek_details.test.tsx.snap | 1448 ++++++++--------- 102 files changed, 2646 insertions(+), 2101 deletions(-) create mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts create mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts create mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts create mode 100644 src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts rename src/legacy/{ui/public/vis/__tests__/index.js => core_plugins/data/public/search/aggs/test_helpers/index.ts} (86%) create mode 100644 src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts create mode 100644 src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts create mode 100644 src/legacy/core_plugins/data/public/search/mocks.ts rename src/legacy/core_plugins/data/public/{actions/filters/brush_event.test.mocks.ts => services.ts} (76%) delete mode 100644 src/legacy/ui/public/vis/__tests__/_agg_config.js delete mode 100644 src/legacy/ui/public/vis/__tests__/_agg_configs.js create mode 100644 src/plugins/data/common/field_formats/mocks.ts diff --git a/packages/kbn-utility-types/README.md b/packages/kbn-utility-types/README.md index 829fd21e143669..b57e98e379707e 100644 --- a/packages/kbn-utility-types/README.md +++ b/packages/kbn-utility-types/README.md @@ -18,6 +18,7 @@ type B = UnwrapPromise; // string ## Reference +- `Assign` — From `U` assign properties to `T` (just like object assign). - `Ensure` — Makes sure `T` is of type `X`. - `ObservableLike` — Minimal interface for an object resembling an `Observable`. - `PublicContract` — Returns an object with public keys only. diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 808935ed4cb5b8..657d9f547de66e 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -18,7 +18,7 @@ */ import { PromiseType } from 'utility-types'; -export { $Values, Required, Optional, Class } from 'utility-types'; +export { $Values, Assign, Class, Optional, Required } from 'utility-types'; /** * A type that may or may not be a `Promise`. diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts index 0e18c7c707fa3f..eb29530f92fee7 100644 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts @@ -19,34 +19,14 @@ import moment from 'moment'; -jest.mock('../../search/aggs', () => ({ - AggConfigs: function AggConfigs() { - return { - createAggConfig: ({ params }: Record) => ({ - params, - getIndexPattern: () => ({ - timeFieldName: 'time', - }), - }), - }; - }, -})); - -jest.mock('../../../../../../plugins/data/public/services', () => ({ - getIndexPatterns: () => { - return { - get: async () => { - return { - id: 'logstash-*', - timeFieldName: 'time', - }; - }, - }; - }, -})); - import { onBrushEvent, BrushEvent } from './brush_event'; +import { mockDataServices } from '../../search/aggs/test_helpers'; +import { IndexPatternsContract } from '../../../../../../plugins/data/public'; +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setIndexPatterns } from '../../../../../../plugins/data/public/services'; + describe('brushEvent', () => { const DAY_IN_MS = 24 * 60 * 60 * 1000; const JAN_01_2014 = 1388559600000; @@ -59,11 +39,28 @@ describe('brushEvent', () => { }, getIndexPattern: () => ({ timeFieldName: 'time', + fields: { + getByName: () => undefined, + filter: () => [], + }, }), }, ]; beforeEach(() => { + mockDataServices(); + setIndexPatterns(({ + ...dataPluginMock.createStartContract().indexPatterns, + get: async () => ({ + id: 'indexPatternId', + timeFieldName: 'time', + fields: { + getByName: () => undefined, + filter: () => [], + }, + }), + } as unknown) as IndexPatternsContract); + baseEvent = { data: { ordered: { diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 8cde5d0a1fc115..8d730d18a17559 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -35,18 +35,18 @@ export { } from '../../../../plugins/data/public'; export { // agg_types - AggParam, - AggParamOption, - DateRangeKey, + AggParam, // only the type is used externally, only in vis editor + AggParamOption, // only the type is used externally + DateRangeKey, // only used in field formatter deserialization, which will live in data IAggConfig, IAggConfigs, IAggType, IFieldParamType, IMetricAggType, - IpRangeKey, + IpRangeKey, // only used in field formatter deserialization, which will live in data ISchemas, - OptionedParamEditorProps, - OptionedValueProp, + OptionedParamEditorProps, // only type is used externally + OptionedValueProp, // only type is used externally } from './search/types'; /** @public static code */ diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index e13e8e34eaebec..e2b8ca5dda78cf 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -36,6 +36,7 @@ import { setOverlays, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../plugins/data/public/services'; +import { setSearchServiceShim } from './services'; import { SELECT_RANGE_ACTION, selectRangeAction } from './actions/select_range_action'; import { VALUE_CLICK_ACTION, valueClickAction } from './actions/value_click_action'; import { @@ -112,6 +113,9 @@ export class DataPlugin } public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { + const search = this.search.start(core); + setSearchServiceShim(search); + setUiSettings(core.uiSettings); setQueryService(data.query); setIndexPatterns(data.indexPatterns); @@ -123,7 +127,7 @@ export class DataPlugin uiActions.attachAction(VALUE_CLICK_TRIGGER, VALUE_CLICK_ACTION); return { - search: this.search.start(core), + search, }; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts new file mode 100644 index 00000000000000..7769aa29184d3a --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.test.ts @@ -0,0 +1,497 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { identity } from 'lodash'; + +import { AggConfig, IAggConfig } from './agg_config'; +import { AggConfigs, CreateAggConfigParams } from './agg_configs'; +import { AggType } from './agg_types'; +import { AggTypesRegistryStart } from './agg_types_registry'; +import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; +import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { stubIndexPatternWithFields } from '../../../../../../plugins/data/public/stubs'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../../../plugins/data/public/services'; + +describe('AggConfig', () => { + let indexPattern: IndexPattern; + let typesRegistry: AggTypesRegistryStart; + + beforeEach(() => { + jest.restoreAllMocks(); + mockDataServices(); + indexPattern = stubIndexPatternWithFields as IndexPattern; + typesRegistry = mockAggTypesRegistry(); + }); + + describe('#toDsl', () => { + it('calls #write()', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + const spy = jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: {} })); + aggConfig.toDsl(); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('uses the type name as the agg name', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: {} })); + const dsl = aggConfig.toDsl(); + expect(dsl).toHaveProperty('date_histogram'); + }); + + it('uses the params from #write() output as the agg params', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + const football = {}; + jest.spyOn(aggConfig, 'write').mockImplementation(() => ({ params: football })); + const dsl = aggConfig.toDsl(); + expect(dsl.date_histogram).toBe(football); + }); + + it('includes subAggs from #write() output', () => { + const configStates = [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ]; + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + + const histoConfig = ac.byName('date_histogram')[0]; + const avgConfig = ac.byName('avg')[0]; + const football = {}; + + jest + .spyOn(histoConfig, 'write') + .mockImplementation(() => ({ params: {}, subAggs: [avgConfig] })); + jest.spyOn(avgConfig, 'write').mockImplementation(() => ({ params: football })); + + const dsl = histoConfig.toDsl(); + expect(dsl).toHaveProperty('aggs'); + expect(dsl.aggs).toHaveProperty(avgConfig.id); + expect(dsl.aggs[avgConfig.id]).toHaveProperty('avg'); + expect(dsl.aggs[avgConfig.id].avg).toBe(football); + }); + }); + + describe('::ensureIds', () => { + it('accepts an array of objects and assigns ids to them', () => { + const objs = [{}, {}, {}, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).toHaveProperty('id', '1'); + expect(objs[1]).toHaveProperty('id', '2'); + expect(objs[2]).toHaveProperty('id', '3'); + expect(objs[3]).toHaveProperty('id', '4'); + }); + + it('assigns ids relative to the other only item in the list', () => { + const objs = [{ id: '100' }, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).toHaveProperty('id', '100'); + expect(objs[1]).toHaveProperty('id', '101'); + }); + + it('assigns ids relative to the other items in the list', () => { + const objs = [{ id: '100' }, { id: '200' }, { id: '500' }, { id: '350' }, {}]; + AggConfig.ensureIds(objs); + expect(objs[0]).toHaveProperty('id', '100'); + expect(objs[1]).toHaveProperty('id', '200'); + expect(objs[2]).toHaveProperty('id', '500'); + expect(objs[3]).toHaveProperty('id', '350'); + expect(objs[4]).toHaveProperty('id', '501'); + }); + + it('uses ::nextId to get the starting value', () => { + jest.spyOn(AggConfig, 'nextId').mockImplementation(() => 534); + const objs = AggConfig.ensureIds([{}]); + expect(objs[0]).toHaveProperty('id', '534'); + }); + + it('only calls ::nextId once', () => { + const start = 420; + const spy = jest.spyOn(AggConfig, 'nextId').mockImplementation(() => start); + const objs = AggConfig.ensureIds([{}, {}, {}, {}, {}, {}, {}]); + + expect(spy).toHaveBeenCalledTimes(1); + objs.forEach((obj, i) => { + expect(obj).toHaveProperty('id', String(start + i)); + }); + }); + }); + + describe('::nextId', () => { + it('accepts a list of objects and picks the next id', () => { + const next = AggConfig.nextId([{ id: '100' }, { id: '500' }] as IAggConfig[]); + expect(next).toBe(501); + }); + + it('handles an empty list', () => { + const next = AggConfig.nextId([]); + expect(next).toBe(1); + }); + + it('fails when the list is not defined', () => { + expect(() => { + AggConfig.nextId((undefined as unknown) as IAggConfig[]); + }).toThrowError(); + }); + }); + + describe('#toJsonDataEquals', () => { + const testsIdentical = [ + [ + { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + ]; + + testsIdentical.forEach((configState, index) => { + it(`identical aggregations (${index})`, () => { + const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry }); + expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); + }); + }); + + const testsIdenticalDifferentOrder = [ + { + config1: [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + config2: [ + { + enabled: true, + schema: 'metric', + type: 'avg', + params: {}, + }, + { + enabled: true, + schema: 'segment', + type: 'date_histogram', + params: {}, + }, + ], + }, + ]; + + testsIdenticalDifferentOrder.forEach((test, index) => { + it(`identical aggregations (${index}) - init json is in different order`, () => { + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); + expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); + }); + }); + + const testsDifferent = [ + { + config1: [ + { + enabled: true, + type: 'avg', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + config2: [ + { + enabled: true, + type: 'max', + schema: 'metric', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + }, + { + config1: [ + { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + ], + config2: [ + { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }, + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ], + }, + ]; + + testsDifferent.forEach((test, index) => { + it(`different aggregations (${index})`, () => { + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); + expect(ac1.jsonDataEquals(ac2.aggs)).toBe(false); + }); + }); + }); + + describe('#toJSON', () => { + it('includes the aggs id, params, type and schema', () => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }; + const aggConfig = ac.createAggConfig(configStates); + + expect(aggConfig.id).toBe('1'); + expect(typeof aggConfig.params).toBe('object'); + expect(aggConfig.type).toBeInstanceOf(AggType); + expect(aggConfig.type).toHaveProperty('name', 'date_histogram'); + expect(typeof aggConfig.schema).toBe('object'); + expect(aggConfig.schema).toHaveProperty('name', 'segment'); + + const state = aggConfig.toJSON(); + expect(state).toHaveProperty('id', '1'); + expect(typeof state.params).toBe('object'); + expect(state).toHaveProperty('type', 'date_histogram'); + expect(state).toHaveProperty('schema', 'segment'); + }); + + it('test serialization order is identical (for visual consistency)', () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: {}, + }, + ]; + const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry }); + + // this relies on the assumption that js-engines consistently loop over properties in insertion order. + // most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. + expect(JSON.stringify(ac1.aggs) === JSON.stringify(ac2.aggs)).toBe(true); + }); + }); + + describe('#makeLabel', () => { + let aggConfig: AggConfig; + + beforeEach(() => { + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + aggConfig = ac.createAggConfig({ type: 'count' } as CreateAggConfigParams); + }); + + it('uses the custom label if it is defined', () => { + aggConfig.params.customLabel = 'Custom label'; + const label = aggConfig.makeLabel(); + expect(label).toBe(aggConfig.params.customLabel); + }); + + it('default label should be "Count"', () => { + const label = aggConfig.makeLabel(); + expect(label).toBe('Count'); + }); + + it('default label should be "Percentage of Count" when percentageMode is set to true', () => { + const label = aggConfig.makeLabel(true); + expect(label).toBe('Percentage of Count'); + }); + + it('empty label if the type is not defined', () => { + aggConfig.type = (undefined as unknown) as AggType; + const label = aggConfig.makeLabel(); + expect(label).toBe(''); + }); + }); + + describe('#fieldFormatter - custom getFormat handler', () => { + it('returns formatter from getFormat handler', () => { + setFieldFormats({ + ...dataPluginMock.createStartContract().fieldFormats, + getDefaultInstance: jest.fn().mockImplementation(() => ({ + getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), + })) as any, + }); + + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); + const configStates = { + enabled: true, + type: 'count', + schema: 'metric', + params: { field: '@timestamp' }, + }; + const aggConfig = ac.createAggConfig(configStates); + + const fieldFormatter = aggConfig.fieldFormatter(); + expect(fieldFormatter).toBeDefined(); + expect(fieldFormatter('text')).toBe('text'); + }); + }); + + // TODO: Converting these field formatter tests from browser tests to unit + // tests makes them much less helpful due to the extensive use of mocking. + // We should revisit these and rewrite them into something more useful. + describe('#fieldFormatter - no custom getFormat handler', () => { + let aggConfig: AggConfig; + + beforeEach(() => { + setFieldFormats({ + ...dataPluginMock.createStartContract().fieldFormats, + getDefaultInstance: jest.fn().mockImplementation(() => ({ + getConverterFor: (t?: string) => t || identity, + })) as any, + }); + indexPattern.fields.getByName = name => + ({ + format: { + getConverterFor: (t?: string) => t || identity, + }, + } as IndexPatternField); + + const configStates = { + enabled: true, + type: 'histogram', + schema: 'bucket', + params: { + field: { + format: { + getConverterFor: (t?: string) => t || identity, + }, + }, + }, + }; + const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry }); + aggConfig = ac.createAggConfig(configStates); + }); + + it("returns the field's formatter", () => { + expect(aggConfig.fieldFormatter().toString()).toBe( + aggConfig + .getField() + .format.getConverterFor() + .toString() + ); + }); + + it('returns the string format if the field does not have a format', () => { + const agg = aggConfig; + agg.params.field = { type: 'number', format: null }; + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).toBeDefined(); + expect(fieldFormatter('text')).toBe('text'); + }); + + it('returns the string format if there is no field', () => { + const agg = aggConfig; + delete agg.params.field; + const fieldFormatter = agg.fieldFormatter(); + expect(fieldFormatter).toBeDefined(); + expect(fieldFormatter('text')).toBe('text'); + }); + + it('returns the html converter if "html" is passed in', () => { + const field = indexPattern.fields.getByName('bytes'); + expect(aggConfig.fieldFormatter('html').toString()).toBe( + field!.format.getConverterFor('html').toString() + ); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts index 2b21c5c4868a52..659bec3f702e37 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts @@ -17,16 +17,8 @@ * under the License. */ -/** - * @name AggConfig - * - * @description This class represents an aggregation, which is displayed in the left-hand nav of - * the Visualize app. - */ - import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { IAggType } from './agg_type'; import { AggGroupNames } from './agg_groups'; import { writeParams } from './agg_params'; @@ -38,18 +30,20 @@ import { FieldFormatsContentType, KBN_FIELD_TYPES, } from '../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../plugins/data/public/services'; export interface AggConfigOptions { - enabled: boolean; - type: string; - params: any; + type: IAggType; + enabled?: boolean; id?: string; - schema?: string; + params?: Record; + schema?: string | Schema; } const unknownSchema: Schema = { name: 'unknown', - title: 'Unknown', + title: 'Unknown', // only here for illustrative purposes hideCustomLabel: true, aggFilter: [], min: 1, @@ -65,21 +59,6 @@ const unknownSchema: Schema = { }, }; -const getTypeFromRegistry = (type: string): IAggType => { - // We need to inline require here, since we're having a cyclic dependency - // from somewhere inside agg_types back to AggConfig. - const aggTypes = require('../aggs').aggTypes; - const registeredType = - aggTypes.metrics.find((agg: IAggType) => agg.name === type) || - aggTypes.buckets.find((agg: IAggType) => agg.name === type); - - if (!registeredType) { - throw new Error('unknown type'); - } - - return registeredType; -}; - const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { let registeredSchema = schemas ? schemas.byName[schema] : null; if (!registeredSchema) { @@ -90,6 +69,13 @@ const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { return registeredSchema; }; +/** + * @name AggConfig + * + * @description This class represents an aggregation, which is displayed in the left-hand nav of + * the Visualize app. + */ + // TODO need to make a more explicit interface for this export type IAggConfig = AggConfig; @@ -101,9 +87,9 @@ export class AggConfig { * @param {array[object]} list - a list of objects, objects can be anything really * @return {array} - the list that was passed in */ - static ensureIds(list: AggConfig[]) { - const have: AggConfig[] = []; - const haveNot: AggConfig[] = []; + static ensureIds(list: any[]) { + const have: IAggConfig[] = []; + const haveNot: AggConfigOptions[] = []; list.forEach(function(obj) { (obj.id ? have : haveNot).push(obj); }); @@ -121,7 +107,7 @@ export class AggConfig { * * @return {array} list - a list of objects with id properties */ - static nextId(list: AggConfig[]) { + static nextId(list: IAggConfig[]) { return ( 1 + list.reduce(function(max, obj) { @@ -161,10 +147,10 @@ export class AggConfig { // set the params to the values from opts, or just to the defaults this.setParams(opts.params || {}); - // @ts-ignore - this.__type = this.__type; // @ts-ignore this.__schema = this.__schema; + // @ts-ignore + this.__type = this.__type; } /** @@ -394,7 +380,8 @@ export class AggConfig { } fieldOwnFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); + const field = this.getField(); let format = field && field.format; if (!format) format = defaultFormat; @@ -456,8 +443,8 @@ export class AggConfig { }); } - public setType(type: string | IAggType) { - this.type = typeof type === 'string' ? getTypeFromRegistry(type) : type; + public setType(type: IAggType) { + this.type = type; } public get schema() { diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts new file mode 100644 index 00000000000000..29f16b1e4f0bf2 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.test.ts @@ -0,0 +1,503 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { indexBy } from 'lodash'; +import { AggConfig } from './agg_config'; +import { AggConfigs } from './agg_configs'; +import { AggTypesRegistryStart } from './agg_types_registry'; +import { Schemas } from './schemas'; +import { AggGroupNames } from './agg_groups'; +import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; +import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; +import { + stubIndexPattern, + stubIndexPatternWithFields, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/stubs'; + +describe('AggConfigs', () => { + let indexPattern: IndexPattern; + let typesRegistry: AggTypesRegistryStart; + + beforeEach(() => { + indexPattern = stubIndexPatternWithFields as IndexPattern; + typesRegistry = mockAggTypesRegistry(); + }); + + describe('constructor', () => { + it('handles passing just a type', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(1); + }); + + it('attempts to ensure that all states have an id', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + params: {}, + }, + { + enabled: true, + type: 'terms', + params: {}, + schema: 'split', + }, + ]; + + const spy = jest.spyOn(AggConfig, 'ensureIds'); + new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0]).toEqual([configStates]); + spy.mockRestore(); + }); + + describe('defaults', () => { + const schemas = new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: 'Simple', + min: 1, + max: 2, + defaults: [ + { schema: 'metric', type: 'count' }, + { schema: 'metric', type: 'avg' }, + { schema: 'metric', type: 'sum' }, + ], + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: 'Example', + min: 0, + max: 1, + defaults: [ + { schema: 'segment', type: 'terms' }, + { schema: 'segment', type: 'filters' }, + ], + }, + ]); + + it('should only set the number of defaults defined by the max', () => { + const ac = new AggConfigs(indexPattern, [], { + schemas: schemas.all, + typesRegistry, + }); + expect(ac.bySchemaName('metric')).toHaveLength(2); + }); + + it('should set the defaults defined in the schema when none exist', () => { + const ac = new AggConfigs(indexPattern, [], { + schemas: schemas.all, + typesRegistry, + }); + expect(ac.aggs).toHaveLength(3); + }); + + it('should NOT set the defaults defined in the schema when some exist', () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + params: {}, + schema: 'segment', + }, + ]; + const ac = new AggConfigs(indexPattern, configStates, { + schemas: schemas.all, + typesRegistry, + }); + expect(ac.aggs).toHaveLength(3); + expect(ac.bySchemaName('segment')[0].type.name).toEqual('date_histogram'); + }); + }); + }); + + describe('#createAggConfig', () => { + it('accepts a configState which is provided as an AggConfig object', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + { + enabled: true, + type: 'date_histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(2); + + ac.createAggConfig( + new AggConfig(ac, { + enabled: true, + type: typesRegistry.get('terms'), + params: {}, + schema: 'split', + }) + ); + expect(ac.aggs).toHaveLength(3); + }); + + it('adds new AggConfig entries to AggConfigs by default', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(1); + + ac.createAggConfig({ + enabled: true, + type: 'terms', + params: {}, + schema: 'split', + }); + expect(ac.aggs).toHaveLength(2); + }); + + it('does not add an agg to AggConfigs if addToAggConfigs: false', () => { + const configStates = [ + { + enabled: true, + type: 'histogram', + params: {}, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + expect(ac.aggs).toHaveLength(1); + + ac.createAggConfig( + { + enabled: true, + type: 'terms', + params: {}, + schema: 'split', + }, + { addToAggConfigs: false } + ); + expect(ac.aggs).toHaveLength(1); + }); + }); + + describe('#getRequestAggs', () => { + it('performs a stable sort, but moves metrics to the bottom', () => { + const configStates = [ + { type: 'avg', enabled: true, params: {}, schema: 'metric' }, + { type: 'terms', enabled: true, params: {}, schema: 'split' }, + { type: 'histogram', enabled: true, params: {}, schema: 'split' }, + { type: 'sum', enabled: true, params: {}, schema: 'metric' }, + { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, + { type: 'filters', enabled: true, params: {}, schema: 'split' }, + { type: 'percentiles', enabled: true, params: {}, schema: 'metric' }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const sorted = ac.getRequestAggs(); + const aggs = indexBy(ac.aggs, agg => agg.type.name); + + expect(sorted.shift()).toBe(aggs.terms); + expect(sorted.shift()).toBe(aggs.histogram); + expect(sorted.shift()).toBe(aggs.date_histogram); + expect(sorted.shift()).toBe(aggs.filters); + expect(sorted.shift()).toBe(aggs.avg); + expect(sorted.shift()).toBe(aggs.sum); + expect(sorted.shift()).toBe(aggs.percentiles); + expect(sorted).toHaveLength(0); + }); + }); + + describe('#getResponseAggs', () => { + it('returns all request aggs for basic aggs', () => { + const configStates = [ + { type: 'terms', enabled: true, params: {}, schema: 'split' }, + { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, + { type: 'count', enabled: true, params: {}, schema: 'metric' }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const sorted = ac.getResponseAggs(); + const aggs = indexBy(ac.aggs, agg => agg.type.name); + + expect(sorted.shift()).toBe(aggs.terms); + expect(sorted.shift()).toBe(aggs.date_histogram); + expect(sorted.shift()).toBe(aggs.count); + expect(sorted).toHaveLength(0); + }); + + it('expands aggs that have multiple responses', () => { + const configStates = [ + { type: 'terms', enabled: true, params: {}, schema: 'split' }, + { type: 'date_histogram', enabled: true, params: {}, schema: 'segment' }, + { type: 'percentiles', enabled: true, params: { percents: [1, 2, 3] }, schema: 'metric' }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const sorted = ac.getResponseAggs(); + const aggs = indexBy(ac.aggs, agg => agg.type.name); + + expect(sorted.shift()).toBe(aggs.terms); + expect(sorted.shift()).toBe(aggs.date_histogram); + expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 1); + expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 2); + expect(sorted.shift()!.id!).toBe(aggs.percentiles.id + '.' + 3); + expect(sorted).toHaveLength(0); + }); + }); + + describe('#toDsl', () => { + const schemas = new Schemas([ + { + group: AggGroupNames.Buckets, + name: 'segment', + }, + { + group: AggGroupNames.Buckets, + name: 'split', + }, + ]); + + beforeEach(() => { + mockDataServices(); + indexPattern = stubIndexPattern as IndexPattern; + indexPattern.fields.getByName = name => (name as unknown) as IndexPatternField; + }); + + it('uses the sorted aggs', () => { + const configStates = [{ enabled: true, type: 'avg', params: { field: 'bytes' } }]; + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const spy = jest.spyOn(AggConfigs.prototype, 'getRequestAggs'); + ac.toDsl(); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); + + it('calls aggConfig#toDsl() on each aggConfig and compiles the nested output', () => { + const configStates = [ + { enabled: true, type: 'date_histogram', params: {}, schema: 'segment' }, + { enabled: true, type: 'terms', params: {}, schema: 'split' }, + { enabled: true, type: 'count', params: {} }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { + typesRegistry, + schemas: schemas.all, + }); + + const aggInfos = ac.aggs.map(aggConfig => { + const football = {}; + aggConfig.toDsl = jest.fn().mockImplementation(() => football); + + return { + id: aggConfig.id, + football, + }; + }); + + (function recurse(lvl: Record): void { + const info = aggInfos.shift(); + if (!info) return; + + expect(lvl).toHaveProperty(info.id); + expect(lvl[info.id]).toBe(info.football); + + if (lvl[info.id].aggs) { + return recurse(lvl[info.id].aggs); + } + })(ac.toDsl()); + + expect(aggInfos).toHaveLength(1); + }); + + it("skips aggs that don't have a dsl representation", () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + params: { field: '@timestamp', interval: '10s' }, + schema: 'segment', + }, + { + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const dsl = ac.toDsl(); + const histo = ac.byName('date_histogram')[0]; + const count = ac.byName('count')[0]; + + expect(dsl).toHaveProperty(histo.id); + expect(typeof dsl[histo.id]).toBe('object'); + expect(dsl[histo.id]).not.toHaveProperty('aggs'); + expect(dsl).not.toHaveProperty(count.id); + }); + + it('writes multiple metric aggregations at the same level', () => { + const configStates = [ + { + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { field: '@timestamp', interval: '10s' }, + }, + { enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'min', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { + typesRegistry, + schemas: schemas.all, + }); + const dsl = ac.toDsl(); + const histo = ac.byName('date_histogram')[0]; + const metrics = ac.bySchemaGroup('metrics'); + + expect(dsl).toHaveProperty(histo.id); + expect(typeof dsl[histo.id]).toBe('object'); + expect(dsl[histo.id]).toHaveProperty('aggs'); + + metrics.forEach(metric => { + expect(dsl[histo.id].aggs).toHaveProperty(metric.id); + expect(dsl[histo.id].aggs[metric.id]).not.toHaveProperty('aggs'); + }); + }); + + it('writes multiple metric aggregations at every level if the vis is hierarchical', () => { + const configStates = [ + { enabled: true, type: 'terms', schema: 'segment', params: { field: 'bytes', orderBy: 1 } }, + { enabled: true, type: 'terms', schema: 'segment', params: { field: 'bytes', orderBy: 1 } }, + { enabled: true, id: '1', type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'min', schema: 'metric', params: { field: 'bytes' } }, + { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const topLevelDsl = ac.toDsl(true); + const buckets = ac.bySchemaGroup('buckets'); + const metrics = ac.bySchemaGroup('metrics'); + + (function checkLevel(dsl) { + const bucket = buckets.shift(); + if (!bucket) return; + + expect(dsl).toHaveProperty(bucket.id); + + expect(typeof dsl[bucket.id]).toBe('object'); + expect(dsl[bucket.id]).toHaveProperty('aggs'); + + metrics.forEach((metric: AggConfig) => { + expect(dsl[bucket.id].aggs).toHaveProperty(metric.id); + expect(dsl[bucket.id].aggs[metric.id]).not.toHaveProperty('aggs'); + }); + + if (buckets.length) { + checkLevel(dsl[bucket.id].aggs); + } + })(topLevelDsl); + }); + + it('adds the parent aggs of nested metrics at every level if the vis is hierarchical', () => { + const configStates = [ + { + enabled: true, + id: '1', + type: 'avg_bucket', + schema: 'metric', + params: { + customBucket: { + id: '1-bucket', + type: 'date_histogram', + schema: 'bucketAgg', + params: { + field: '@timestamp', + interval: '10s', + }, + }, + customMetric: { + id: '1-metric', + type: 'count', + schema: 'metricAgg', + params: {}, + }, + }, + }, + { + enabled: true, + id: '2', + type: 'terms', + schema: 'bucket', + params: { + field: 'clientip', + }, + }, + { + enabled: true, + id: '3', + type: 'terms', + schema: 'bucket', + params: { + field: 'machine.os.raw', + }, + }, + ]; + + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const topLevelDsl = ac.toDsl(true)['2']; + + expect(Object.keys(topLevelDsl.aggs)).toContain('1'); + expect(Object.keys(topLevelDsl.aggs)).toContain('1-bucket'); + expect(topLevelDsl.aggs['1'].avg_bucket).toHaveProperty('buckets_path', '1-bucket>_count'); + expect(Object.keys(topLevelDsl.aggs['3'].aggs)).toContain('1'); + expect(Object.keys(topLevelDsl.aggs['3'].aggs)).toContain('1-bucket'); + expect(topLevelDsl.aggs['3'].aggs['1'].avg_bucket).toHaveProperty( + 'buckets_path', + '1-bucket>_count' + ); + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts index 8e091ed5f21ae0..ab70e66b1e138c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts @@ -17,17 +17,12 @@ * under the License. */ -/** - * @name AggConfig - * - * @extends IndexedArray - * - * @description A "data structure"-like class with methods for indexing and - * accessing instances of AggConfig. - */ - import _ from 'lodash'; +import { Assign } from '@kbn/utility-types'; + import { AggConfig, AggConfigOptions, IAggConfig } from './agg_config'; +import { IAggType } from './agg_type'; +import { AggTypesRegistryStart } from './agg_types_registry'; import { Schema } from './schemas'; import { AggGroupNames } from './agg_groups'; import { @@ -55,6 +50,24 @@ function parseParentAggs(dslLvlCursor: any, dsl: any) { } } +export interface AggConfigsOptions { + schemas?: Schemas; + typesRegistry: AggTypesRegistryStart; +} + +export type CreateAggConfigParams = Assign; + +/** + * @name AggConfigs + * + * @description A "data structure"-like class with methods for indexing and + * accessing instances of AggConfig. This should never be instantiated directly + * outside of this plugin. Rather, downstream plugins should do this via + * `createAggConfigs()` + * + * @internal + */ + // TODO need to make a more explicit interface for this export type IAggConfigs = AggConfigs; @@ -62,23 +75,31 @@ export class AggConfigs { public indexPattern: IndexPattern; public schemas: any; public timeRange?: TimeRange; + private readonly typesRegistry: AggTypesRegistryStart; aggs: IAggConfig[]; - constructor(indexPattern: IndexPattern, configStates = [] as any, schemas?: any) { + constructor( + indexPattern: IndexPattern, + configStates: CreateAggConfigParams[] = [], + opts: AggConfigsOptions + ) { + this.typesRegistry = opts.typesRegistry; + configStates = AggConfig.ensureIds(configStates); this.aggs = []; this.indexPattern = indexPattern; - this.schemas = schemas; + this.schemas = opts.schemas; configStates.forEach((params: any) => this.createAggConfig(params)); - if (schemas) { - this.initializeDefaultsFromSchemas(schemas); + if (this.schemas) { + this.initializeDefaultsFromSchemas(this.schemas); } } + // do this wherever the schemas were passed in, & pass in state defaults instead initializeDefaultsFromSchemas(schemas: Schemas) { // Set the defaults for any schema which has them. If the defaults // for some reason has more then the max only set the max number @@ -91,10 +112,11 @@ export class AggConfigs { }) .each((schema: any) => { if (!this.aggs.find((agg: AggConfig) => agg.schema && agg.schema.name === schema.name)) { + // the result here should be passable as a configState const defaults = schema.defaults.slice(0, schema.max); _.each(defaults, defaultState => { const state = _.defaults({ id: AggConfig.nextId(this.aggs) }, defaultState); - this.aggs.push(new AggConfig(this, state as AggConfigOptions)); + this.createAggConfig(state as AggConfigOptions); }); } }) @@ -124,28 +146,36 @@ export class AggConfigs { if (!enabledOnly) return true; return agg.enabled; }; - const aggConfigs = new AggConfigs( - this.indexPattern, - this.aggs.filter(filterAggs), - this.schemas - ); + + const aggConfigs = new AggConfigs(this.indexPattern, this.aggs.filter(filterAggs), { + schemas: this.schemas, + typesRegistry: this.typesRegistry, + }); + return aggConfigs; } createAggConfig = ( - params: AggConfig | AggConfigOptions, + params: CreateAggConfigParams, { addToAggConfigs = true } = {} ) => { + const { type } = params; let aggConfig; + if (params instanceof AggConfig) { aggConfig = params; params.parent = this; } else { - aggConfig = new AggConfig(this, params); + aggConfig = new AggConfig(this, { + ...params, + type: typeof type === 'string' ? this.typesRegistry.get(type) : type, + }); } + if (addToAggConfigs) { this.aggs.push(aggConfig); } + return aggConfig as T; }; @@ -166,10 +196,10 @@ export class AggConfigs { return true; } - toDsl(hierarchical: boolean = false) { + toDsl(hierarchical: boolean = false): Record { const dslTopLvl = {}; let dslLvlCursor: Record; - let nestedMetrics: Array<{ config: AggConfig; dsl: any }> | []; + let nestedMetrics: Array<{ config: AggConfig; dsl: Record }> | []; if (hierarchical) { // collect all metrics, and filter out the ones that we won't be copying diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts index 30ab272537dad1..b08fcf309e9ed6 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts @@ -23,8 +23,6 @@ import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; import { AggParamType } from '../aggs/param_types/agg'; -jest.mock('ui/new_platform'); - describe('AggParams class', () => { describe('constructor args', () => { it('accepts an array of param defs', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts index 6d4c2d1317f505..c78e56dd25887d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts @@ -19,11 +19,16 @@ import { AggType, AggTypeConfig } from './agg_type'; import { IAggConfig } from './agg_config'; -import { npStart } from 'ui/new_platform'; - -jest.mock('ui/new_platform'); +import { mockDataServices } from './test_helpers'; +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../../../plugins/data/public/services'; describe('AggType Class', () => { + beforeEach(() => { + mockDataServices(); + }); + describe('constructor', () => { it("requires a valid config object as it's first param", () => { expect(() => { @@ -153,7 +158,10 @@ describe('AggType Class', () => { }); it('returns default formatter', () => { - npStart.plugins.data.fieldFormats.getDefaultInstance = jest.fn(() => 'default') as any; + setFieldFormats({ + ...dataPluginMock.createStartContract().fieldFormats, + getDefaultInstance: jest.fn(() => 'default') as any, + }); const aggType = new AggType({ name: 'name', diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts index 5ccf0f65c0e921..3cd9496d3f23d0 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts @@ -19,7 +19,6 @@ import { constant, noop, identity } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { initParams } from './agg_params'; import { AggConfig } from './agg_config'; @@ -32,6 +31,8 @@ import { IFieldFormat, ISearchSource, } from '../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../plugins/data/public/services'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -65,7 +66,7 @@ export interface AggTypeConfig< const getFormat = (agg: AggConfig) => { const field = agg.getField(); - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts new file mode 100644 index 00000000000000..405f83e237de83 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.test.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + AggTypesRegistry, + AggTypesRegistrySetup, + AggTypesRegistryStart, +} from './agg_types_registry'; +import { BucketAggType } from './buckets/_bucket_agg_type'; +import { MetricAggType } from './metrics/metric_agg_type'; + +const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType; +const metricType = { name: 'count', type: 'metric' } as MetricAggType; + +describe('AggTypesRegistry', () => { + let registry: AggTypesRegistry; + let setup: AggTypesRegistrySetup; + let start: AggTypesRegistryStart; + + beforeEach(() => { + registry = new AggTypesRegistry(); + setup = registry.setup(); + start = registry.start(); + }); + + it('registerBucket adds new buckets', () => { + setup.registerBucket(bucketType); + expect(start.getBuckets()).toEqual([bucketType]); + }); + + it('registerBucket throws error when registering duplicate bucket', () => { + expect(() => { + setup.registerBucket(bucketType); + setup.registerBucket(bucketType); + }).toThrow(/already been registered with name: terms/); + }); + + it('registerMetric adds new metrics', () => { + setup.registerMetric(metricType); + expect(start.getMetrics()).toEqual([metricType]); + }); + + it('registerMetric throws error when registering duplicate metric', () => { + expect(() => { + setup.registerMetric(metricType); + setup.registerMetric(metricType); + }).toThrow(/already been registered with name: count/); + }); + + it('gets either buckets or metrics by id', () => { + setup.registerBucket(bucketType); + setup.registerMetric(metricType); + expect(start.get('terms')).toEqual(bucketType); + expect(start.get('count')).toEqual(metricType); + }); + + it('getBuckets retrieves only buckets', () => { + setup.registerBucket(bucketType); + expect(start.getBuckets()).toEqual([bucketType]); + }); + + it('getMetrics retrieves only metrics', () => { + setup.registerMetric(metricType); + expect(start.getMetrics()).toEqual([metricType]); + }); + + it('getAll returns all buckets and metrics', () => { + setup.registerBucket(bucketType); + setup.registerMetric(metricType); + expect(start.getAll()).toEqual({ + buckets: [bucketType], + metrics: [metricType], + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts new file mode 100644 index 00000000000000..8a8746106ae587 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_types_registry.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BucketAggType } from './buckets/_bucket_agg_type'; +import { MetricAggType } from './metrics/metric_agg_type'; + +export type AggTypesRegistrySetup = ReturnType; +export type AggTypesRegistryStart = ReturnType; + +export class AggTypesRegistry { + private readonly bucketAggs = new Map(); + private readonly metricAggs = new Map(); + + setup = () => { + return { + registerBucket: >(type: T): void => { + const { name } = type; + if (this.bucketAggs.get(name)) { + throw new Error(`Bucket agg has already been registered with name: ${name}`); + } + this.bucketAggs.set(name, type); + }, + registerMetric: >(type: T): void => { + const { name } = type; + if (this.metricAggs.get(name)) { + throw new Error(`Metric agg has already been registered with name: ${name}`); + } + this.metricAggs.set(name, type); + }, + }; + }; + + start = () => { + return { + get: (name: string) => { + return this.bucketAggs.get(name) || this.metricAggs.get(name); + }, + getBuckets: () => { + return Array.from(this.bucketAggs.values()); + }, + getMetrics: () => { + return Array.from(this.metricAggs.values()); + }, + getAll: () => { + return { + buckets: Array.from(this.bucketAggs.values()), + metrics: Array.from(this.metricAggs.values()), + }; + }, + }; + }; +} diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts index 546d054c5af978..d6ab58d5250a8c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts @@ -17,16 +17,16 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; -export interface IBucketAggConfig extends AggConfig { +export interface IBucketAggConfig extends IAggConfig { type: InstanceType; } -export interface BucketAggParam +export interface BucketAggParam extends AggParamType { scriptable?: boolean; filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; @@ -34,12 +34,12 @@ export interface BucketAggParam const bucketType = 'buckets'; -interface BucketAggTypeConfig +interface BucketAggTypeConfig extends AggTypeConfig> { - getKey?: (bucket: any, key: any, agg: AggConfig) => any; + getKey?: (bucket: any, key: any, agg: IAggConfig) => any; } -export class BucketAggType extends AggType< +export class BucketAggType extends AggType< TBucketAggConfig, BucketAggParam > { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts index e196687607d198..393d3b745250f4 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { IBucketAggConfig } from './_bucket_agg_type'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 0d3f58c50a42e7..2b47dc384bca2f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -21,14 +21,22 @@ import moment from 'moment'; import { createFilterDateHistogram } from './date_histogram'; import { intervalOptions } from '../_interval_options'; import { AggConfigs } from '../../agg_configs'; -import { IBucketDateHistogramAggConfig } from '../date_histogram'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; +import { dateHistogramBucketAgg, IBucketDateHistogramAggConfig } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../../../../plugins/data/public'; +// TODO: remove this once time buckets is migrated jest.mock('ui/new_platform'); describe('AggConfig Filters', () => { describe('date_histogram', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([dateHistogramBucketAgg]); + let agg: IBucketDateHistogramAggConfig; let filter: RangeFilter; let bucketStart: any; @@ -56,7 +64,7 @@ describe('AggConfig Filters', () => { params: { field: field.name, interval, customInterval: '5d' }, }, ], - null + { typesRegistry } ); const bucketKey = 1422579600000; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 41e806668337e2..c594c7718e58bb 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -18,16 +18,17 @@ */ import moment from 'moment'; +import { dateRangeBucketAgg } from '../date_range'; import { createFilterDateRange } from './date_range'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('Date range', () => { + const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -55,7 +56,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 34cf996826865f..3b9c771e0f15f9 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -16,14 +16,21 @@ * specific language governing permissions and limitations * under the License. */ + +import { filtersBucketAgg } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('filters', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([filtersBucketAgg]); + const getAggConfigs = () => { const field = { name: 'bytes', @@ -52,7 +59,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; it('should return a filters filter', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index 9f845847df5d9d..b046c802c58c15 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -16,16 +16,22 @@ * specific language governing permissions and limitations * under the License. */ + import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('histogram', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -55,7 +61,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index e92ba5cb2852a1..7572c48390dc25 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -17,17 +17,18 @@ * under the License. */ +import { ipRangeBucketAgg } from '../ip_range'; import { createFilterIpRange } from './ip_range'; -import { AggConfigs } from '../../agg_configs'; +import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('IP range', () => { - const getAggConfigs = (aggs: Array>) => { + const typesRegistry = mockAggTypesRegistry([ipRangeBucketAgg]); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const field = { name: 'ip', format: fieldFormats.IpFormat, @@ -42,7 +43,7 @@ describe('AggConfig Filters', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, null); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; it('should return a range filter for ip_range agg', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 33344ca0a34845..324d4252908324 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -17,16 +17,22 @@ * under the License. */ +import { rangeBucketAgg } from '../range'; import { createFilterRange } from './range'; import { fieldFormats, FieldFormatsGetConfigFn } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('range', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -56,7 +62,7 @@ describe('AggConfig Filters', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 7c6e769437ca1d..6db6eb11a5f527 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -17,17 +17,18 @@ * under the License. */ +import { termsBucketAgg } from '../terms'; import { createFilterTerms } from './terms'; -import { AggConfigs } from '../../agg_configs'; +import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { Filter, ExistsFilter } from '../../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - describe('AggConfig Filters', () => { describe('terms', () => { - const getAggConfigs = (aggs: Array>) => { + const typesRegistry = mockAggTypesRegistry([termsBucketAgg]); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const indexPattern = { id: '1234', title: 'logstash-*', @@ -42,7 +43,7 @@ describe('AggConfig Filters', () => { indexPattern, }; - return new AggConfigs(indexPattern, aggs, null); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; it('should return a match_phrase filter for terms', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts index dc0f9baa6d0cc7..a5368135728d49 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -21,8 +21,7 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { timefilter } from 'ui/timefilter'; +// TODO need to move TimeBuckets import { TimeBuckets } from 'ui/time_buckets'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -33,6 +32,8 @@ import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getQueryService, getUiSettings } from '../../../../../../../plugins/data/public/services'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -40,6 +41,7 @@ const tzOffset = moment().format('Z'); const getInterval = (agg: IBucketAggConfig): string => _.get(agg, ['params', 'interval']); export const setBounds = (agg: IBucketDateHistogramAggConfig, force?: boolean) => { + const { timefilter } = getQueryService().timefilter; if (agg.buckets._alreadySet && !force) return; agg.buckets._alreadySet = true; const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; @@ -221,7 +223,7 @@ export const dateHistogramBucketAgg = new BucketAggType { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([dateRangeBucketAgg]); + const getAggConfigs = (params: Record = {}, hasIncludeTypeMeta: boolean = true) => { const field = { name: 'bytes', @@ -58,7 +67,7 @@ describe('date_range params', () => { params, }, ], - null + { typesRegistry } ); }; @@ -95,7 +104,11 @@ describe('date_range params', () => { }); it('should use the Kibana time_zone if no parameter specified', () => { - npStart.core.uiSettings.get = jest.fn(() => 'kibanaTimeZone' as any); + const core = coreMock.createStart(); + setUiSettings({ + ...core.uiSettings, + get: () => 'kibanaTimeZone' as any, + }); const aggConfigs = getAggConfigs( { @@ -106,6 +119,8 @@ describe('date_range params', () => { const dateRange = aggConfigs.aggs[0]; const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE]; + setUiSettings(core.uiSettings); // clean up + expect(params.time_zone).toBe('kibanaTimeZone'); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts index 1dc24ca80035c0..933cdd0577f8da 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts @@ -16,18 +16,20 @@ * specific language governing permissions and limitations * under the License. */ + import { get } from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats, getUiSettings } from '../../../../../../../plugins/data/public/services'; -export { convertDateRangeToString, DateRangeKey }; +import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; +export { convertDateRangeToString, DateRangeKey }; // for BWC const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -41,7 +43,7 @@ export const dateRangeBucketAgg = new BucketAggType({ return { from, to }; }, getFormat(agg) { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const formatter = agg.fieldOwnFormatter( fieldFormats.TEXT_CONTEXT_TYPE, @@ -92,7 +94,7 @@ export const dateRangeBucketAgg = new BucketAggType({ ]); } if (!tz) { - const config = npStart.core.uiSettings; + const config = getUiSettings(); const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); const isDefaultTimezone = config.isDefault('dateFormat:tz'); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts index b52e2d6cfd4df4..80efc0cf92071d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts index 6eaf788b83c04a..2852f3e4bdf464 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts @@ -18,19 +18,21 @@ */ import _ from 'lodash'; -import angular from 'angular'; - import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; + import { createFilterFilters } from './create_filter/filters'; +import { toAngularJSON } from '../utils'; import { BucketAggType } from './_bucket_agg_type'; +import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../../plugins/kibana_utils/public'; + import { getQueryLog, esQuery, Query } from '../../../../../../../plugins/data/public'; -import { BUCKET_TYPES } from './bucket_agg_types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getUiSettings } from '../../../../../../../plugins/data/public/services'; const config = chrome.getUiSettingsClient(); -const storage = new Storage(window.localStorage); const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -52,15 +54,17 @@ export const filtersBucketAgg = new BucketAggType({ params: [ { name: 'filters', + // TODO need to get rid of reference to `config` below default: [{ input: { query: '', language: config.get('search:queryLanguage') }, label: '' }], write(aggConfig, output) { + const uiSettings = getUiSettings(); const inFilters: FilterValue[] = aggConfig.params.filters; if (!_.size(inFilters)) return; inFilters.forEach(filter => { const persistedLog = getQueryLog( - config, - storage, + uiSettings, + new Storage(window.localStorage), 'vis_default_editor', filter.input.language ); @@ -77,7 +81,13 @@ export const filtersBucketAgg = new BucketAggType({ return; } - const query = esQuery.buildEsQuery(aggConfig.getIndexPattern(), [input], [], config); + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); + const query = esQuery.buildEsQuery( + aggConfig.getIndexPattern(), + [input], + [], + esQueryConfigs + ); if (!query) { console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console @@ -90,7 +100,7 @@ export const filtersBucketAgg = new BucketAggType({ matchAllLabel || (typeof filter.input.query === 'string' ? filter.input.query - : angular.toJson(filter.input.query)); + : toAngularJSON(filter.input.query)); filters[label] = { query }; }, {} diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts index f0ad5954764867..09dd03c759155e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -19,12 +19,13 @@ import { geoHashBucketAgg } from './geo_hash'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('Geohash Agg', () => { + // const typesRegistry = mockAggTypesRegistry([geoHashBucketAgg]); + const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params?: Record) => { const indexPattern = { id: '1234', @@ -62,7 +63,7 @@ describe('Geohash Agg', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts index 57e8f6e8c5ded4..9142a30338163c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; -import { AggConfigOptions } from '../agg_config'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -57,7 +56,7 @@ export const geoTileBucketAgg = new BucketAggType({ aggs.push(agg); if (useGeocentroid) { - const aggConfig: AggConfigOptions = { + const aggConfig = { type: METRIC_TYPES.GEO_CENTROID, enabled: true, params: { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts index 4e89d7db1ff647..11dc8e42fd6538 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,16 +17,23 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; -import { AggConfigs } from '../index'; +import { AggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram'; import { BucketAggType } from './_bucket_agg_type'; - -jest.mock('ui/new_platform'); +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setUiSettings } from '../../../../../../../plugins/data/public/services'; describe('Histogram Agg', () => { - const getAggConfigs = (params: Record = {}) => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([histogramBucketAgg]); + + const getAggConfigs = (params: Record) => { const indexPattern = { id: '1234', title: 'logstash-*', @@ -45,16 +52,13 @@ describe('Histogram Agg', () => { indexPattern, [ { - field: { - name: 'field', - }, id: 'test', type: BUCKET_TYPES.HISTOGRAM, schema: 'segment', params, }, ], - null + { typesRegistry } ); }; @@ -158,10 +162,15 @@ describe('Histogram Agg', () => { aggConfig.setAutoBounds(autoBounds); } - // mock histogram:maxBars value; - npStart.core.uiSettings.get = jest.fn(() => maxBars as any); + const core = coreMock.createStart(); + setUiSettings({ + ...core.uiSettings, + get: () => maxBars as any, + }); - return aggConfig.write(aggConfigs).params; + const interval = aggConfig.write(aggConfigs).params; + setUiSettings(core.uiSettings); // clean up + return interval; }; it('will respect the histogram:maxBars setting', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts index f7e9ef45961e04..70df2f230db094 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts @@ -19,13 +19,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -import { npStart } from 'ui/new_platform'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getNotifications, getUiSettings } from '../../../../../../../plugins/data/public/services'; export interface AutoBounds { min: number; @@ -37,8 +37,6 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { getAutoBounds: () => AutoBounds; } -const getUIConfig = () => npStart.core.uiSettings; - export const histogramBucketAgg = new BucketAggType({ name: BUCKET_TYPES.HISTOGRAM, title: i18n.translate('data.search.aggs.buckets.histogramTitle', { @@ -116,7 +114,7 @@ export const histogramBucketAgg = new BucketAggType({ }) .catch((e: Error) => { if (e.name === 'AbortError') return; - toastNotifications.addWarning( + getNotifications().toasts.addWarning( i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { defaultMessage: 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', @@ -136,7 +134,7 @@ export const histogramBucketAgg = new BucketAggType({ const range = autoBounds.max - autoBounds.min; const bars = range / interval; - const config = getUIConfig(); + const config = getUiSettings(); if (bars > config.get('histogram:maxBars')) { const minInterval = range / config.get('histogram:maxBars'); diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts index 91bdf53e7f8091..3fb464d8fa7a8b 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts @@ -19,15 +19,17 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -// @ts-ignore import { createFilterIpRange } from './create_filter/ip_range'; import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; -export { IpRangeKey, convertIPRangeToString }; + +import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; +export { IpRangeKey, convertIPRangeToString }; // for BWC + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', @@ -44,7 +46,7 @@ export const ipRangeBucketAgg = new BucketAggType({ return { type: 'range', from: bucket.from, to: bucket.to }; }, getFormat(agg) { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const formatter = agg.fieldOwnFormatter( fieldFormats.TEXT_CONTEXT_TYPE, fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.IP) diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index 77e84e044de55a..d94477b588f8d1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -19,10 +19,10 @@ import { isString, isObject } from 'lodash'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; export const isType = (type: string) => { - return (agg: AggConfig): boolean => { + return (agg: IAggConfig): boolean => { const field = agg.params.field; return field && field.type === type; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts index b1b0c4bc30a58e..096b19fe7de66d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts @@ -17,12 +17,12 @@ * under the License. */ +import { rangeBucketAgg } from './range'; import { AggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, fieldFormats } from '../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - const buckets = [ { to: 1024, @@ -44,6 +44,12 @@ const buckets = [ ]; describe('Range Agg', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); + const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -80,7 +86,7 @@ describe('Range Agg', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 37b829bfc20fb2..cee3ed506c29c4 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -17,17 +17,16 @@ * under the License. */ -import { AggConfigs } from '../index'; -import { IAggConfigs } from '../types'; +import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { significantTermsBucketAgg } from './significant_terms'; import { IBucketAggConfig } from './_bucket_agg_type'; -jest.mock('ui/new_platform'); - describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { + const typesRegistry = mockAggTypesRegistry([significantTermsBucketAgg]); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -53,7 +52,7 @@ describe('Significant Terms Agg', () => { params, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts index 24ac332ae4d55c..9a4f28afd3edf2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts @@ -17,13 +17,13 @@ * under the License. */ -import { AggConfigs } from '../index'; +import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -jest.mock('ui/new_platform'); - describe('Terms Agg', () => { describe('order agg editor UI', () => { + const typesRegistry = mockAggTypesRegistry(); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -48,7 +48,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts index cc1288d339692f..0de1c31d02f961 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts @@ -19,13 +19,12 @@ import { IndexPattern } from '../../../../../../../plugins/data/public'; import { AggTypeFilters } from './agg_type_filters'; -import { AggConfig } from '..'; -import { IAggType } from '../types'; +import { IAggConfig, IAggType } from '../types'; describe('AggTypeFilters', () => { let registry: AggTypeFilters; const indexPattern = ({ id: '1234', fields: [], title: 'foo' } as unknown) as IndexPattern; - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; beforeEach(() => { registry = new AggTypeFilters(); diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts index d3b38ce041d7ee..13a4cc0856b090 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { IndexPattern } from 'src/plugins/data/public'; import { IAggConfig, IAggType } from '../types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts index 431e1161e0dbdf..32cda7b950e93d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts @@ -17,7 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; import { propFilter } from './prop_filter'; describe('prop filter', () => { @@ -47,48 +46,48 @@ describe('prop filter', () => { it('returns list when no filters are provided', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects)).to.eql(objects); + expect(nameFilter(objects)).toEqual(objects); }); it('returns list when empty list of filters is provided', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects, [])).to.eql(objects); + expect(nameFilter(objects, [])).toEqual(objects); }); it('should keep only the tables', () => { const objects = getObjects('table', 'table', 'pie'); - expect(nameFilter(objects, 'table')).to.eql(getObjects('table', 'table')); + expect(nameFilter(objects, 'table')).toEqual(getObjects('table', 'table')); }); it('should support comma-separated values', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, 'table,line')).to.eql(getObjects('table', 'line')); + expect(nameFilter(objects, 'table,line')).toEqual(getObjects('table', 'line')); }); it('should support an array of values', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, ['table', 'line'])).to.eql(getObjects('table', 'line')); + expect(nameFilter(objects, ['table', 'line'])).toEqual(getObjects('table', 'line')); }); it('should return all objects', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, '*')).to.eql(objects); + expect(nameFilter(objects, '*')).toEqual(objects); }); it('should allow negation', () => { const objects = getObjects('table', 'line', 'pie'); - expect(nameFilter(objects, ['!line'])).to.eql(getObjects('table', 'pie')); + expect(nameFilter(objects, ['!line'])).toEqual(getObjects('table', 'pie')); }); it('should support a function for specifying what should be kept', () => { const objects = getObjects('table', 'line', 'pie'); const line = (value: string) => value === 'line'; - expect(nameFilter(objects, line)).to.eql(getObjects('line')); + expect(nameFilter(objects, line)).toEqual(getObjects('line')); }); it('gracefully handles a filter function with zero arity', () => { const objects = getObjects('table', 'line', 'pie'); const rejectEverything = () => false; - expect(nameFilter(objects, rejectEverything)).to.eql([]); + expect(nameFilter(objects, rejectEverything)).toEqual([]); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.test.ts b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts index a867769a77fc1d..4d0cd55b09d533 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/index.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts @@ -25,8 +25,6 @@ import { isMetricAggType } from './metrics/metric_agg_type'; const bucketAggs = aggTypes.buckets; const metricAggs = aggTypes.metrics; -jest.mock('ui/new_platform'); - describe('AggTypesComponent', () => { describe('bucket aggs', () => { it('all extend BucketAggType', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.ts b/src/legacy/core_plugins/data/public/search/aggs/index.ts index 0bdb92b8de65e8..f6914c36f6c05c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/index.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/index.ts @@ -17,8 +17,13 @@ * under the License. */ -export { aggTypes } from './agg_types'; +export { + AggTypesRegistry, + AggTypesRegistrySetup, + AggTypesRegistryStart, +} from './agg_types_registry'; export { AggType } from './agg_type'; +export { aggTypes } from './agg_types'; export { AggConfig } from './agg_config'; export { AggConfigs } from './agg_configs'; export { FieldParamType } from './param_types'; @@ -52,4 +57,4 @@ export { METRIC_TYPES } from './metrics/metric_agg_types'; export { ISchemas, Schema, Schemas } from './schemas'; // types -export { IAggConfig, IAggConfigs } from './types'; +export { CreateAggConfigParams, IAggConfig, IAggConfigs } from './types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts index 9fb28f8631bc63..11bb5592747297 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; - import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts index 83837f0de51146..0668a9bcf57a8f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; - import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts index d96197693dc2ee..8f728cb5e7e426 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts index 147e9255210887..4f7b6e555ca33e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts @@ -18,10 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', @@ -37,7 +38,7 @@ export const cardinalityMetricAgg = new MetricAggType({ }); }, getFormat() { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts index 14a9bd073ff2bc..8b3e0a488c68a1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts @@ -18,10 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; export const countMetricAgg = new MetricAggType({ name: METRIC_TYPES.COUNT, @@ -35,7 +36,7 @@ export const countMetricAgg = new MetricAggType({ }); }, getFormat() { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }, diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts index 054543de3dd067..00d866e6f2b3ed 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { assign } from 'lodash'; import { IMetricAggConfig } from '../metric_agg_type'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts index e24aca08271c72..88549ee3019ee6 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts @@ -23,7 +23,6 @@ import { noop, identity } from 'lodash'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { parentPipelineAggWriter } from './parent_pipeline_agg_writer'; - import { Schemas } from '../../schemas'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index e7c98e575fdb4f..05e009cc9da30e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -21,7 +21,6 @@ import { identity } from 'lodash'; import { i18n } from '@kbn/i18n'; import { siblingPipelineAggWriter } from './sibling_pipeline_agg_writer'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; - import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { Schemas } from '../../schemas'; import { fieldFormats } from '../../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts index 4755a873e69779..ad55837ec9a300 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,15 +17,16 @@ * under the License. */ +import { medianMetricAgg } from './median'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; beforeEach(() => { + const typesRegistry = mockAggTypesRegistry([medianMetricAgg]); const field = { name: 'bytes', }; @@ -50,7 +51,7 @@ describe('AggTypeMetricMedianProvider class', () => { }, }, ], - null + { typesRegistry } ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts index 53a5ffff418f10..68fc98261118c2 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts @@ -16,12 +16,10 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; - -// @ts-ignore -import { percentilesMetricAgg } from './percentiles'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 3bae7b92618dcd..952dcc96de8330 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -18,13 +18,14 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; +import { FilterFieldTypes } from '../param_types/field'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -import { FilterFieldTypes } from '../param_types/field'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; export interface IMetricAggConfig extends AggConfig { type: InstanceType; @@ -78,7 +79,7 @@ export class MetricAggType { - const fieldFormatsService = npStart.plugins.data.fieldFormats; + const fieldFormatsService = getFieldFormats(); const field = agg.getField(); return field ? field.format diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts index 48851051634356..1806c6d9d77107 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 11fc39c20bdc42..58b4ee530a8c2d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -17,12 +17,12 @@ * under the License. */ -import sinon from 'sinon'; import { derivativeMetricAgg } from './derivative'; import { cumulativeSumMetricAgg } from './cumulative_sum'; import { movingAvgMetricAgg } from './moving_avg'; import { serialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; jest.mock('../schemas', () => { @@ -34,9 +34,13 @@ jest.mock('../schemas', () => { }; }); -jest.mock('ui/new_platform'); - describe('parent pipeline aggs', function() { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const metrics = [ { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, @@ -94,7 +98,7 @@ describe('parent pipeline aggs', function() { schema: 'metric', }, ], - null + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -220,16 +224,16 @@ describe('parent pipeline aggs', function() { }); const searchSource: any = {}; - const customMetricSpy = sinon.spy(); + const customMetricSpy = jest.fn(); const customMetric = aggConfig.params.customMetric; // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 655e918ce07deb..628f1cd204ee5e 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -19,14 +19,16 @@ import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; beforeEach(() => { + mockDataServices(); + + const typesRegistry = mockAggTypesRegistry([percentileRanksMetricAgg]); const field = { name: 'bytes', }; @@ -58,7 +60,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }, }, ], - null + { typesRegistry } ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts index 38b47a7e97d2f2..1d640a9c1fa42f 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -18,20 +18,17 @@ */ import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; - import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getFieldFormats } from '../../../../../../../plugins/data/public/services'; // required by the values editor - export type IPercentileRanksAggConfig = IResponseAggConfig; -const getFieldFormats = () => npStart.plugins.data.fieldFormats; - const valueProps = { makeLabel(this: IPercentileRanksAggConfig) { const fieldFormatsService = getFieldFormats(); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts index dd1aaca973e473..e077bc0f8c7737 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -19,14 +19,14 @@ import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; beforeEach(() => { + const typesRegistry = mockAggTypesRegistry([percentilesMetricAgg]); const field = { name: 'bytes', }; @@ -58,7 +58,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }, }, ], - null + { typesRegistry } ); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts index 39dc0d0f181e92..49e927d07d8dd0 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts @@ -18,15 +18,11 @@ */ import { i18n } from '@kbn/i18n'; - import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; - import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; - -// @ts-ignore import { ordinalSuffix } from './lib/ordinal_suffix'; export type IPercentileAggConfig = IResponseAggConfig; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index d643cf0d2a4781..d3456bacceb6ab 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -17,7 +17,6 @@ * under the License. */ -import { spy } from 'sinon'; import { bucketSumMetricAgg } from './bucket_sum'; import { bucketAvgMetricAgg } from './bucket_avg'; import { bucketMinMetricAgg } from './bucket_min'; @@ -25,6 +24,7 @@ import { bucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; jest.mock('../schemas', () => { class MockedSchemas { @@ -35,9 +35,13 @@ jest.mock('../schemas', () => { }; }); -jest.mock('ui/new_platform'); - describe('sibling pipeline aggs', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const metrics = [ { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, @@ -96,7 +100,7 @@ describe('sibling pipeline aggs', () => { }, }, ], - null + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -162,8 +166,8 @@ describe('sibling pipeline aggs', () => { init(); const searchSource: any = {}; - const customMetricSpy = spy(); - const customBucketSpy = spy(); + const customMetricSpy = jest.fn(); + const customBucketSpy = jest.fn(); const { customMetric, customBucket } = aggConfig.params; // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter @@ -171,11 +175,11 @@ describe('sibling pipeline aggs', () => { customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource, {}); }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); - expect(customBucketSpy.calledWith(customBucket, searchSource)).toBe(true); + expect(customMetricSpy.mock.calls[0]).toEqual([customMetric, searchSource, {}]); + expect(customBucketSpy.mock.calls[0]).toEqual([customBucket, searchSource, {}]); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 3125026a521854..0679831b1e6ac8 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -19,11 +19,11 @@ import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -jest.mock('ui/new_platform'); - describe('AggTypeMetricStandardDeviationProvider class', () => { + const typesRegistry = mockAggTypesRegistry([stdDeviationMetricAgg]); const getAggConfigs = (customLabel?: string) => { const field = { name: 'memory', @@ -52,7 +52,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { }, }, ], - null + { typesRegistry } ); }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts index a973de4fe8659e..ad1f42f5c563e6 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -20,11 +20,10 @@ import { dropRight, last } from 'lodash'; import { topHitMetricAgg } from './top_hit'; import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -jest.mock('ui/new_platform'); - describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; @@ -37,6 +36,7 @@ describe('Top hit metric', () => { fieldType = KBN_FIELD_TYPES.NUMBER, size = 1, }: any) => { + const typesRegistry = mockAggTypesRegistry([topHitMetricAgg]); const field = { name: fieldName, displayName: fieldName, @@ -81,7 +81,7 @@ describe('Top hit metric', () => { params, }, ], - null + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts index 2e7c11004b472e..d31abe64491d0d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts @@ -17,10 +17,10 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { AggConfig, IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; -export class AggParamType extends BaseParamType< +export class AggParamType extends BaseParamType< TAggConfig > { makeAgg: (agg: TAggConfig, state?: any) => TAggConfig; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts index 1523cb03eb966c..95ad71a616ab27 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts @@ -18,10 +18,10 @@ */ import { IAggConfigs } from '../agg_configs'; -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { FetchOptions, ISearchSource } from '../../../../../../../plugins/data/public'; -export class BaseParamType { +export class BaseParamType { name: string; type: string; displayName: string; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts index fa88754ac60b94..7338c41f920d72 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts @@ -25,8 +25,6 @@ import { IAggConfig } from '../agg_config'; import { IMetricAggConfig } from '../metrics/metric_agg_type'; import { Schema } from '../schemas'; -jest.mock('ui/new_platform'); - describe('Field', () => { const indexPattern = { id: '1234', diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts index 40c30f6210a833..bb5707cbb482ec 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts @@ -19,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { isFunction } from 'lodash'; -import { npStart } from 'ui/new_platform'; import { IAggConfig } from '../agg_config'; import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; import { BaseParamType } from './base'; @@ -30,6 +29,8 @@ import { indexPatterns, KBN_FIELD_TYPES, } from '../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getNotifications } from '../../../../../../../plugins/data/public/services'; const filterByType = propFilter('type'); @@ -93,7 +94,7 @@ export class FieldParamType extends BaseParamType { // @ts-ignore const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); if (!validField) { - npStart.core.notifications.toasts.addDanger( + getNotifications().toasts.addDanger( i18n.translate( 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts index bc36bb46d3d166..1a453a225797db 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts @@ -17,27 +17,26 @@ * under the License. */ -import { IndexedArray } from 'ui/indexed_array'; import { AggTypeFieldFilters } from './field_filters'; -import { AggConfig } from '../../agg_config'; +import { IAggConfig } from '../../agg_config'; import { IndexPatternField } from '../../../../../../../../plugins/data/public'; describe('AggTypeFieldFilters', () => { let registry: AggTypeFieldFilters; - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; beforeEach(() => { registry = new AggTypeFieldFilters(); }); it('should filter nothing without registered filters', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; const filtered = registry.filter(fields, aggConfig); expect(filtered).toEqual(fields); }); it('should pass all fields to the registered filter', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; const filter = jest.fn(); registry.addFilter(filter); registry.filter(fields, aggConfig); @@ -46,7 +45,7 @@ describe('AggTypeFieldFilters', () => { }); it('should allow registered filters to filter out fields', async () => { - const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexedArray; + const fields = [{ name: 'foo' }, { name: 'bar' }] as IndexPatternField[]; let filtered = registry.filter(fields, aggConfig); expect(filtered).toEqual(fields); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts index 7d1348ab5423be..1cbf0c9ae36245 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts @@ -17,9 +17,9 @@ * under the License. */ import { IndexPatternField } from 'src/plugins/data/public'; -import { AggConfig } from '../../agg_config'; +import { IAggConfig } from '../../agg_config'; -type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: AggConfig) => boolean; +type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: IAggConfig) => boolean; /** * A registry to store {@link AggTypeFieldFilter} which are used to filter down @@ -41,11 +41,11 @@ class AggTypeFieldFilters { /** * Returns the {@link any|fields} filtered by all registered filters. * - * @param fields An IndexedArray of fields that will be filtered down by this registry. + * @param fields An array of fields that will be filtered down by this registry. * @param aggConfig The aggConfig for which the returning list will be used. * @return A filtered list of the passed fields. */ - public filter(fields: IndexPatternField[], aggConfig: AggConfig) { + public filter(fields: IndexPatternField[], aggConfig: IAggConfig) { const allFilters = Array.from(this.filters); const allowedAggTypeFields = fields.filter(field => { const isAggTypeFieldAllowed = allFilters.every(filter => filter(field, aggConfig)); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts index 827299814c62aa..12fd29b3a14527 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts @@ -19,13 +19,11 @@ import { BaseParamType } from './base'; import { JsonParamType } from './json'; -import { AggConfig } from '../agg_config'; - -jest.mock('ui/new_platform'); +import { IAggConfig } from '../agg_config'; describe('JSON', function() { const paramName = 'json_test'; - let aggConfig: AggConfig; + let aggConfig: IAggConfig; let output: Record; const initAggParam = (config: Record = {}) => @@ -36,7 +34,7 @@ describe('JSON', function() { }); beforeEach(function() { - aggConfig = { params: {} } as AggConfig; + aggConfig = { params: {} } as IAggConfig; output = { params: {} }; }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts index 771919b0bb56b9..bf85b3b890c358 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; export class JsonParamType extends BaseParamType { @@ -29,7 +29,7 @@ export class JsonParamType extends BaseParamType { this.name = config.name || 'json'; if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: IAggConfig, output: Record) => { let paramJson; const param = aggConfig.params[this.name]; diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts index 6b58d81914097c..c03d6cdfa1c700 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts @@ -20,8 +20,6 @@ import { BaseParamType } from './base'; import { OptionedParamType } from './optioned'; -jest.mock('ui/new_platform'); - describe('Optioned', () => { describe('constructor', () => { it('it is an instance of BaseParamType', () => { diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts index 5ffda3740af49f..9eb7ceda607117 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts @@ -17,14 +17,14 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; export interface OptionedValueProp { value: string; text: string; disabled?: boolean; - isCompatible: (agg: AggConfig) => boolean; + isCompatible: (agg: IAggConfig) => boolean; } export interface OptionedParamEditorProps { @@ -40,7 +40,7 @@ export class OptionedParamType extends BaseParamType { super(config); if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: IAggConfig, output: Record) => { output.params[this.name] = aggConfig.params[this.name].value; }; } diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts index fd5ccebde993eb..29ec9741611a37 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts @@ -19,13 +19,11 @@ import { BaseParamType } from './base'; import { StringParamType } from './string'; -import { AggConfig } from '../agg_config'; - -jest.mock('ui/new_platform'); +import { IAggConfig } from '../agg_config'; describe('String', function() { let paramName = 'json_test'; - let aggConfig: AggConfig; + let aggConfig: IAggConfig; let output: Record; const initAggParam = (config: Record = {}) => @@ -36,7 +34,7 @@ describe('String', function() { }); beforeEach(() => { - aggConfig = { params: {} } as AggConfig; + aggConfig = { params: {} } as IAggConfig; output = { params: {} }; }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts index 58ba99f8a6d636..750606eb8433bf 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AggConfig } from '../agg_config'; +import { IAggConfig } from '../agg_config'; import { BaseParamType } from './base'; export class StringParamType extends BaseParamType { @@ -25,7 +25,7 @@ export class StringParamType extends BaseParamType { super(config); if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: IAggConfig, output: Record) => { if (aggConfig.params[this.name] && aggConfig.params[this.name].length) { output.params[this.name] = aggConfig.params[this.name]; } diff --git a/src/legacy/ui/public/vis/__tests__/index.js b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts similarity index 86% rename from src/legacy/ui/public/vis/__tests__/index.js rename to src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts index 46074f2c5197b7..131f921586144c 100644 --- a/src/legacy/ui/public/vis/__tests__/index.js +++ b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/index.ts @@ -17,5 +17,5 @@ * under the License. */ -import './_agg_config'; -import './_agg_configs'; +export { mockAggTypesRegistry } from './mock_agg_types_registry'; +export { mockDataServices } from './mock_data_services'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts new file mode 100644 index 00000000000000..d6bb7938664932 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; +import { aggTypes } from '../agg_types'; +import { BucketAggType } from '../buckets/_bucket_agg_type'; +import { MetricAggType } from '../metrics/metric_agg_type'; + +/** + * Testing utility which creates a new instance of AggTypesRegistry, + * registers the provided agg types, and returns AggTypesRegistry.start() + * + * This is useful if your test depends on a certain agg type to be present + * in the registry. + * + * @param [types] - Optional array of AggTypes to register. + * If no value is provided, all default types will be registered. + * + * @internal + */ +export function mockAggTypesRegistry | MetricAggType>( + types?: T[] +): AggTypesRegistryStart { + const registry = new AggTypesRegistry(); + const registrySetup = registry.setup(); + + if (types) { + types.forEach(type => { + if (type instanceof BucketAggType) { + registrySetup.registerBucket(type); + } else if (type instanceof MetricAggType) { + registrySetup.registerMetric(type); + } + }); + } else { + aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); + aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); + } + + return registry.start(); +} diff --git a/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts new file mode 100644 index 00000000000000..c4e78ab8f64226 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/test_helpers/mock_data_services.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../plugins/data/public/mocks'; +import { searchStartMock } from '../../mocks'; +import { setSearchServiceShim } from '../../../services'; +import { + setFieldFormats, + setIndexPatterns, + setNotifications, + setOverlays, + setQueryService, + setSearchService, + setUiSettings, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/data/public/services'; + +/** + * Testing helper which calls all of the service setters used in the + * data plugin. Services are added using their provided mocks. + * + * @internal + */ +export function mockDataServices() { + const core = coreMock.createStart(); + const data = dataPluginMock.createStartContract(); + const searchShim = searchStartMock(); + + setSearchServiceShim(searchShim); + setFieldFormats(data.fieldFormats); + setIndexPatterns(data.indexPatterns); + setNotifications(core.notifications); + setOverlays(core.overlays); + setQueryService(data.query); + setSearchService(data.search); + setUiSettings(core.uiSettings); +} diff --git a/src/legacy/core_plugins/data/public/search/aggs/types.ts b/src/legacy/core_plugins/data/public/search/aggs/types.ts index 2c918abf99fcaf..5d02f426b58967 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/types.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/types.ts @@ -18,7 +18,7 @@ */ export { IAggConfig } from './agg_config'; -export { IAggConfigs } from './agg_configs'; +export { CreateAggConfigParams, IAggConfigs } from './agg_configs'; export { IAggType } from './agg_type'; export { AggParam, AggParamOption } from './agg_params'; export { IFieldParamType } from './param_types'; diff --git a/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx index a3c7f24f3927d8..c0662c98755a3d 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx @@ -19,8 +19,6 @@ import { isValidJson } from './utils'; -jest.mock('ui/new_platform'); - const input = { valid: '{ "test": "json input" }', invalid: 'strings are not json', diff --git a/src/legacy/core_plugins/data/public/search/aggs/utils.ts b/src/legacy/core_plugins/data/public/search/aggs/utils.ts index 62f07ce44ab46e..67ea373f438fb8 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/utils.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.ts @@ -26,7 +26,7 @@ import { isValidEsInterval } from '../../../common'; * @param {string} value a string that should be validated * @returns {boolean} true if value is a valid JSON or if value is an empty string, or a string with whitespaces, otherwise false */ -function isValidJson(value: string): boolean { +export function isValidJson(value: string): boolean { if (!value || value.length === 0) { return true; } @@ -49,7 +49,7 @@ function isValidJson(value: string): boolean { } } -function isValidInterval(value: string, baseInterval?: string) { +export function isValidInterval(value: string, baseInterval?: string) { if (baseInterval) { return _parseWithBase(value, baseInterval); } else { @@ -69,4 +69,37 @@ function _parseWithBase(value: string, baseInterval: string) { } } -export { isValidJson, isValidInterval }; +// An inlined version of angular.toJSON() +// source: https://github.com/angular/angular.js/blob/master/src/Angular.js#L1312 +// @internal +export function toAngularJSON(obj: any, pretty?: any): string { + if (obj === undefined) return ''; + if (typeof pretty === 'number') { + pretty = pretty ? 2 : null; + } + return JSON.stringify(obj, toJsonReplacer, pretty); +} + +function isWindow(obj: any) { + return obj && obj.window === obj; +} + +function isScope(obj: any) { + return obj && obj.$evalAsync && obj.$watch; +} + +function toJsonReplacer(key: any, value: any) { + let val = value; + + if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { + val = undefined; + } else if (isWindow(value)) { + val = '$WINDOW'; + } else if (value && window.document === value) { + val = '$DOCUMENT'; + } else if (isScope(value)) { + val = '$SCOPE'; + } + + return val; +} diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 7a5d927d0f219a..24dd1c4944bfba 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -19,7 +19,7 @@ import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { AggConfigs, IAggConfigs } from 'ui/agg_types'; +import { createAggConfigs, IAggConfigs } from 'ui/agg_types'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { KibanaContext, @@ -258,7 +258,7 @@ export const esaggs = (): ExpressionFunctionDefinition { - const aggConfigs = new AggConfigs(indexPattern); + const { aggs } = getSearchServiceShim(); + const aggConfigs = aggs.createAggConfigs(indexPattern); const aggConfig = aggConfigs.createAggConfig({ enabled: true, type, diff --git a/src/legacy/core_plugins/data/public/search/mocks.ts b/src/legacy/core_plugins/data/public/search/mocks.ts new file mode 100644 index 00000000000000..86b6a928dc5b42 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/mocks.ts @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SearchSetup, SearchStart } from './search_service'; +import { AggTypesRegistrySetup, AggTypesRegistryStart } from './aggs/agg_types_registry'; +import { AggConfigs } from './aggs/agg_configs'; +import { mockAggTypesRegistry } from './aggs/test_helpers'; + +const aggTypeBaseParamMock = () => ({ + name: 'some_param', + type: 'some_param_type', + displayName: 'some_agg_type_param', + required: false, + advanced: false, + default: {}, + write: jest.fn(), + serialize: jest.fn().mockImplementation(() => {}), + deserialize: jest.fn().mockImplementation(() => {}), + options: [], +}); + +const aggTypeConfigMock = () => ({ + name: 'some_name', + title: 'some_title', + params: [aggTypeBaseParamMock()], +}); + +export const aggTypesRegistrySetupMock = (): MockedKeys => ({ + registerBucket: jest.fn(), + registerMetric: jest.fn(), +}); + +export const aggTypesRegistryStartMock = (): MockedKeys => ({ + get: jest.fn().mockImplementation(aggTypeConfigMock), + getBuckets: jest.fn().mockImplementation(() => [aggTypeConfigMock()]), + getMetrics: jest.fn().mockImplementation(() => [aggTypeConfigMock()]), + getAll: jest.fn().mockImplementation(() => ({ + buckets: [aggTypeConfigMock()], + metrics: [aggTypeConfigMock()], + })), +}); + +export const searchSetupMock = (): MockedKeys => ({ + aggs: { + types: aggTypesRegistrySetupMock(), + }, +}); + +export const searchStartMock = (): MockedKeys => ({ + aggs: { + createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { + return new AggConfigs(indexPattern, configStates, { + schemas, + typesRegistry: mockAggTypesRegistry(), + }); + }), + types: mockAggTypesRegistry(), + __LEGACY: { + AggConfig: jest.fn() as any, + AggType: jest.fn(), + aggTypeFieldFilters: jest.fn() as any, + FieldParamType: jest.fn(), + MetricAggType: jest.fn(), + parentPipelineAggHelper: jest.fn() as any, + setBounds: jest.fn(), + siblingPipelineAggHelper: jest.fn() as any, + }, + }, +}); diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/legacy/core_plugins/data/public/search/search_service.ts index 45f9ff17328adb..6754c0e3551af5 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/src/legacy/core_plugins/data/public/search/search_service.ts @@ -18,11 +18,16 @@ */ import { CoreSetup, CoreStart } from '../../../../../core/public'; +import { IndexPattern } from '../../../../../plugins/data/public'; import { aggTypes, AggType, + AggTypesRegistry, + AggTypesRegistrySetup, + AggTypesRegistryStart, AggConfig, AggConfigs, + CreateAggConfigParams, FieldParamType, MetricAggType, aggTypeFieldFilters, @@ -32,20 +37,28 @@ import { } from './aggs'; interface AggsSetup { - types: typeof aggTypes; + types: AggTypesRegistrySetup; } -interface AggsStart { - types: typeof aggTypes; +interface AggsStartLegacy { AggConfig: typeof AggConfig; - AggConfigs: typeof AggConfigs; AggType: typeof AggType; aggTypeFieldFilters: typeof aggTypeFieldFilters; FieldParamType: typeof FieldParamType; MetricAggType: typeof MetricAggType; parentPipelineAggHelper: typeof parentPipelineAggHelper; - siblingPipelineAggHelper: typeof siblingPipelineAggHelper; setBounds: typeof setBounds; + siblingPipelineAggHelper: typeof siblingPipelineAggHelper; +} + +interface AggsStart { + createAggConfigs: ( + indexPattern: IndexPattern, + configStates?: CreateAggConfigParams[], + schemas?: Record + ) => InstanceType; + types: AggTypesRegistryStart; + __LEGACY: AggsStartLegacy; } export interface SearchSetup { @@ -63,28 +76,41 @@ export interface SearchStart { * it will move into the existing search service in src/plugins/data/public/search */ export class SearchService { + private readonly aggTypesRegistry = new AggTypesRegistry(); + public setup(core: CoreSetup): SearchSetup { + const aggTypesSetup = this.aggTypesRegistry.setup(); + aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); + aggTypes.metrics.forEach(m => aggTypesSetup.registerMetric(m)); + return { aggs: { - types: aggTypes, // TODO convert to registry - // TODO add other items as needed + types: aggTypesSetup, }, }; } public start(core: CoreStart): SearchStart { + const aggTypesStart = this.aggTypesRegistry.start(); return { aggs: { - types: aggTypes, // TODO convert to registry - AggConfig, // TODO make static - AggConfigs, - AggType, - aggTypeFieldFilters, - FieldParamType, - MetricAggType, - parentPipelineAggHelper, // TODO make static - siblingPipelineAggHelper, // TODO make static - setBounds, // TODO make static + createAggConfigs: (indexPattern, configStates = [], schemas) => { + return new AggConfigs(indexPattern, configStates, { + schemas, + typesRegistry: aggTypesStart, + }); + }, + types: aggTypesStart, + __LEGACY: { + AggConfig, // TODO make static + AggType, + aggTypeFieldFilters, + FieldParamType, + MetricAggType, + parentPipelineAggHelper, // TODO make static + setBounds, // TODO make static + siblingPipelineAggHelper, // TODO make static + }, }, }; } diff --git a/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts b/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts index ef2748102623ab..98048cb25db2fe 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/buckets.test.ts @@ -20,8 +20,6 @@ import { TabifyBuckets } from './buckets'; import { AggGroupNames } from '../aggs'; -jest.mock('ui/new_platform'); - describe('Buckets wrapper', () => { const check = (aggResp: any, count: number, keys: string[]) => { test('reads the length', () => { diff --git a/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts b/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts index cfd4cd7de640b2..6c5dc790ef9767 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/get_columns.test.ts @@ -18,12 +18,17 @@ */ import { tabifyGetColumns } from './get_columns'; -import { AggConfigs, AggGroupNames, Schemas } from '../aggs'; import { TabbedAggColumn } from './types'; - -jest.mock('ui/new_platform'); +import { AggConfigs, AggGroupNames, Schemas } from '../aggs'; +import { mockAggTypesRegistry, mockDataServices } from '../aggs/test_helpers'; describe('get columns', () => { + beforeEach(() => { + mockDataServices(); + }); + + const typesRegistry = mockAggTypesRegistry(); + const createAggConfigs = (aggs: any[] = []) => { const field = { name: '@timestamp', @@ -38,18 +43,17 @@ describe('get columns', () => { }, } as any; - return new AggConfigs( - indexPattern, - aggs, - new Schemas([ + return new AggConfigs(indexPattern, aggs, { + typesRegistry, + schemas: new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all - ); + ]).all, + }); }; test('should inject a count metric if no aggs exist', () => { diff --git a/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts b/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts index f5df0a683ca00c..94301eedac74a7 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/response_writer.test.ts @@ -19,14 +19,19 @@ import { TabbedAggResponseWriter } from './response_writer'; import { AggConfigs, AggGroupNames, Schemas, BUCKET_TYPES } from '../aggs'; +import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; import { TabbedResponseWriterOptions } from './types'; -jest.mock('ui/new_platform'); - describe('TabbedAggResponseWriter class', () => { + beforeEach(() => { + mockDataServices(); + }); + let responseWriter: TabbedAggResponseWriter; + const typesRegistry = mockAggTypesRegistry(); + const splitAggConfig = [ { type: BUCKET_TYPES.TERMS, @@ -66,18 +71,17 @@ describe('TabbedAggResponseWriter class', () => { } as any; return new TabbedAggResponseWriter( - new AggConfigs( - indexPattern, - aggs, - new Schemas([ + new AggConfigs(indexPattern, aggs, { + typesRegistry, + schemas: new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all - ), + ]).all, + }), { metricsAtAllLevels: false, partialRows: false, diff --git a/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts b/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts index 13fe7719b0a856..db4ad3bdea96bd 100644 --- a/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts +++ b/src/legacy/core_plugins/data/public/search/tabify/tabify.test.ts @@ -20,11 +20,12 @@ import { IndexPattern } from '../../../../../../plugins/data/public'; import { tabifyAggResponse } from './tabify'; import { IAggConfig, IAggConfigs, AggGroupNames, Schemas, AggConfigs } from '../aggs'; +import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data'; -jest.mock('ui/new_platform'); - describe('tabifyAggResponse Integration', () => { + const typesRegistry = mockAggTypesRegistry(); + const createAggConfigs = (aggs: IAggConfig[] = []) => { const field = { name: '@timestamp', @@ -39,18 +40,17 @@ describe('tabifyAggResponse Integration', () => { }, } as unknown) as IndexPattern; - return new AggConfigs( - indexPattern, - aggs, - new Schemas([ + return new AggConfigs(indexPattern, aggs, { + typesRegistry, + schemas: new Schemas([ { group: AggGroupNames.Metrics, name: 'metric', min: 1, defaults: [{ schema: 'metric', type: 'count' }], }, - ]).all - ); + ]).all, + }); }; const mockAggConfig = (agg: any): IAggConfig => (agg as unknown) as IAggConfig; diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts b/src/legacy/core_plugins/data/public/services.ts similarity index 76% rename from src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts rename to src/legacy/core_plugins/data/public/services.ts index 2cecfd0fe8b767..7ecd041c70e22e 100644 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts +++ b/src/legacy/core_plugins/data/public/services.ts @@ -17,12 +17,9 @@ * under the License. */ -import { chromeServiceMock } from '../../../../../../core/public/mocks'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; +import { SearchStart } from './search/search_service'; -jest.doMock('ui/new_platform', () => ({ - npStart: { - core: { - chrome: chromeServiceMock.createStartContract(), - }, - }, -})); +export const [getSearchServiceShim, setSearchServiceShim] = createGetterSetter( + 'searchShim' +); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts index 6591aa5fb53d5f..6ae4e415f8caa8 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts @@ -20,7 +20,8 @@ import { cloneDeep } from 'lodash'; import { Vis, VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfigs, IAggConfig, AggGroupNames } from '../../../legacy_imports'; + +import { createAggConfigs, IAggConfig, AggGroupNames } from '../../../legacy_imports'; import { EditorStateActionTypes } from './constants'; import { getEnabledMetricAggsCount } from '../../agg_group_helper'; import { EditorAction } from './actions'; @@ -32,7 +33,8 @@ function initEditorState(vis: Vis) { function editorStateReducer(state: VisState, action: EditorAction): VisState { switch (action.type) { case EditorStateActionTypes.ADD_NEW_AGG: { - const aggConfig = state.aggs.createAggConfig(action.payload as IAggConfig, { + const payloadAggConfig = action.payload as IAggConfig; + const aggConfig = state.aggs.createAggConfig(payloadAggConfig, { addToAggConfigs: false, }); aggConfig.brandNew = true; @@ -40,7 +42,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -63,7 +65,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -88,7 +90,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -129,7 +131,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -141,7 +143,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } @@ -163,7 +165,7 @@ function editorStateReducer(state: VisState, action: EditorAction): VisState { return { ...state, - aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + aggs: createAggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), }; } diff --git a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts index 832f73752a99b6..8aed263c4e4d1d 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts @@ -22,12 +22,12 @@ export { AggType, IAggType, IAggConfig, - AggConfigs, IAggConfigs, AggParam, AggGroupNames, aggGroupNamesMap, aggTypes, + createAggConfigs, FieldParamType, IFieldParamType, BUCKET_TYPES, diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index 0e1e48d00a1b27..736152c7014dc2 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -34,7 +34,7 @@ import { stubFields } from '../../../../plugins/data/public/stubs'; import { tableVisResponseHandler } from './table_vis_response_handler'; import { coreMock } from '../../../../core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AggConfigs } from 'ui/agg_types'; +import { createAggConfigs } from 'ui/agg_types'; import { tabifyAggResponse, IAggConfig } from './legacy_imports'; jest.mock('ui/new_platform'); @@ -113,7 +113,7 @@ describe('Table Vis - Controller', () => { return ({ type: tableVisTypeDefinition, params: Object.assign({}, tableVisTypeDefinition.visConfig.defaults, params), - aggs: new AggConfigs( + aggs: createAggConfigs( stubIndexPattern, [ { type: 'count', schema: 'metric' }, diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index fb7a157b53a9ac..0a3b1938436c0e 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -18,10 +18,10 @@ */ export { - AggConfigs, IAggConfig, IAggConfigs, isDateHistogramBucketAggConfig, setBounds, } from '../../data/public'; +export { createAggConfigs } from 'ui/agg_types'; export { createSavedSearchesLoader } from '../../../../plugins/discover/public'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js index 2f36322c67256a..15a826cc6ddbe5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js @@ -30,7 +30,7 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; import { PersistedState } from '../../../../../../../src/plugins/visualizations/public'; -import { AggConfigs } from '../../legacy_imports'; +import { createAggConfigs } from '../../legacy_imports'; import { updateVisualizationConfig } from './legacy/vis_update'; import { getTypes } from './services'; @@ -83,7 +83,7 @@ class VisImpl extends EventEmitter { updateVisualizationConfig(state.params, this.params); if (state.aggs || !this.aggs) { - this.aggs = new AggConfigs( + this.aggs = createAggConfigs( this.indexPattern, state.aggs ? state.aggs.aggs || state.aggs : [], this.type.schemas.all @@ -125,7 +125,7 @@ class VisImpl extends EventEmitter { copyCurrentState(includeDisabled = false) { const state = this.getCurrentState(includeDisabled); - state.aggs = new AggConfigs( + state.aggs = createAggConfigs( this.indexPattern, state.aggs.aggs || state.aggs, this.type.schemas.all diff --git a/src/legacy/ui/public/agg_types/index.ts b/src/legacy/ui/public/agg_types/index.ts index ac5d0bed7ef153..ffc300251c4bb3 100644 --- a/src/legacy/ui/public/agg_types/index.ts +++ b/src/legacy/ui/public/agg_types/index.ts @@ -27,18 +27,19 @@ import { start as dataStart } from '../../../core_plugins/data/public/legacy'; // runtime contracts +const { types } = dataStart.search.aggs; +export const aggTypes = types.getAll(); +export const { createAggConfigs } = dataStart.search.aggs; export const { - types: aggTypes, AggConfig, - AggConfigs, AggType, aggTypeFieldFilters, FieldParamType, MetricAggType, parentPipelineAggHelper, - siblingPipelineAggHelper, setBounds, -} = dataStart.search.aggs; + siblingPipelineAggHelper, +} = dataStart.search.aggs.__LEGACY; // types export { diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js deleted file mode 100644 index 9e53044f681baa..00000000000000 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { AggType, AggConfig } from '../../agg_types'; -import { start as visualizationsStart } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; - -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggConfig', function() { - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - }) - ); - - describe('#toDsl', function() { - it('calls #write()', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - const stub = sinon.stub(aggConfig, 'write').returns({ params: {} }); - - aggConfig.toDsl(); - expect(stub.callCount).to.be(1); - }); - - it('uses the type name as the agg name', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - sinon.stub(aggConfig, 'write').returns({ params: {} }); - - const dsl = aggConfig.toDsl(); - expect(dsl).to.have.property('date_histogram'); - }); - - it('uses the params from #write() output as the agg params', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - const football = {}; - - sinon.stub(aggConfig, 'write').returns({ params: football }); - - const dsl = aggConfig.toDsl(); - expect(dsl.date_histogram).to.be(football); - }); - - it('includes subAggs from #write() output', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const histoConfig = vis.aggs.byName('date_histogram')[0]; - const avgConfig = vis.aggs.byName('avg')[0]; - const football = {}; - - sinon.stub(histoConfig, 'write').returns({ params: {}, subAggs: [avgConfig] }); - sinon.stub(avgConfig, 'write').returns({ params: football }); - - const dsl = histoConfig.toDsl(); - - // didn't use .eql() because of variable key names, and final check is strict - expect(dsl).to.have.property('aggs'); - expect(dsl.aggs).to.have.property(avgConfig.id); - expect(dsl.aggs[avgConfig.id]).to.have.property('avg'); - expect(dsl.aggs[avgConfig.id].avg).to.be(football); - }); - }); - - describe('::ensureIds', function() { - it('accepts an array of objects and assigns ids to them', function() { - const objs = [{}, {}, {}, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).to.have.property('id', '1'); - expect(objs[1]).to.have.property('id', '2'); - expect(objs[2]).to.have.property('id', '3'); - expect(objs[3]).to.have.property('id', '4'); - }); - - it('assigns ids relative to the other only item in the list', function() { - const objs = [{ id: '100' }, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).to.have.property('id', '100'); - expect(objs[1]).to.have.property('id', '101'); - }); - - it('assigns ids relative to the other items in the list', function() { - const objs = [{ id: '100' }, { id: '200' }, { id: '500' }, { id: '350' }, {}]; - AggConfig.ensureIds(objs); - expect(objs[0]).to.have.property('id', '100'); - expect(objs[1]).to.have.property('id', '200'); - expect(objs[2]).to.have.property('id', '500'); - expect(objs[3]).to.have.property('id', '350'); - expect(objs[4]).to.have.property('id', '501'); - }); - - it('uses ::nextId to get the starting value', function() { - sinon.stub(AggConfig, 'nextId').returns(534); - const objs = AggConfig.ensureIds([{}]); - AggConfig.nextId.restore(); - expect(objs[0]).to.have.property('id', '534'); - }); - - it('only calls ::nextId once', function() { - const start = 420; - sinon.stub(AggConfig, 'nextId').returns(start); - const objs = AggConfig.ensureIds([{}, {}, {}, {}, {}, {}, {}]); - - expect(AggConfig.nextId).to.have.property('callCount', 1); - - AggConfig.nextId.restore(); - objs.forEach(function(obj, i) { - expect(obj).to.have.property('id', String(start + i)); - }); - }); - }); - - describe('::nextId', function() { - it('accepts a list of objects and picks the next id', function() { - const next = AggConfig.nextId([{ id: 100 }, { id: 500 }]); - expect(next).to.be(501); - }); - - it('handles an empty list', function() { - const next = AggConfig.nextId([]); - expect(next).to.be(1); - }); - - it('fails when the list is not defined', function() { - expect(function() { - AggConfig.nextId(); - }).to.throwError(); - }); - }); - - describe('#toJsonDataEquals', function() { - const testsIdentical = [ - { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - }, - { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - ]; - - testsIdentical.forEach((visConfig, index) => { - it(`identical aggregations (${index})`, function() { - const vis1 = new visualizationsStart.Vis(indexPattern, visConfig); - const vis2 = new visualizationsStart.Vis(indexPattern, visConfig); - expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(true); - }); - }); - - const testsIdenticalDifferentOrder = [ - { - config1: { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - config2: { - type: 'histogram', - aggs: [ - { - schema: 'metric', - type: 'avg', - }, - { - schema: 'segment', - type: 'date_histogram', - }, - ], - }, - }, - ]; - - testsIdenticalDifferentOrder.forEach((test, index) => { - it(`identical aggregations (${index}) - init json is in different order`, function() { - const vis1 = new visualizationsStart.Vis(indexPattern, test.config1); - const vis2 = new visualizationsStart.Vis(indexPattern, test.config2); - expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(true); - }); - }); - - const testsDifferent = [ - { - config1: { - type: 'histogram', - aggs: [ - { - type: 'avg', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - config2: { - type: 'histogram', - aggs: [ - { - type: 'max', - schema: 'metric', - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - }, - { - config1: { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - }, - config2: { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }, - }, - ]; - - testsDifferent.forEach((test, index) => { - it(`different aggregations (${index})`, function() { - const vis1 = new visualizationsStart.Vis(indexPattern, test.config1); - const vis2 = new visualizationsStart.Vis(indexPattern, test.config2); - expect(vis1.aggs.jsonDataEquals(vis2.aggs.aggs)).to.be(false); - }); - }); - }); - - describe('#toJSON', function() { - it('includes the aggs id, params, type and schema', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - - const aggConfig = vis.aggs.byName('date_histogram')[0]; - expect(aggConfig.id).to.be('1'); - expect(aggConfig.params).to.be.an('object'); - expect(aggConfig.type) - .to.be.an(AggType) - .and.have.property('name', 'date_histogram'); - expect(aggConfig.schema) - .to.be.an('object') - .and.have.property('name', 'segment'); - - const state = aggConfig.toJSON(); - expect(state).to.have.property('id', '1'); - expect(state.params).to.be.an('object'); - expect(state).to.have.property('type', 'date_histogram'); - expect(state).to.have.property('schema', 'segment'); - }); - - it('test serialization order is identical (for visual consistency)', function() { - const vis1 = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - }, - ], - }); - const vis2 = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - schema: 'segment', - type: 'date_histogram', - }, - ], - }); - - //this relies on the assumption that js-engines consistently loop over properties in insertion order. - //most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. - expect(JSON.stringify(vis1.aggs.aggs) === JSON.stringify(vis2.aggs.aggs)).to.be(true); - }); - }); - - describe('#makeLabel', function() { - it('uses the custom label if it is defined', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'Custom label'; - const label = aggConfig.makeLabel(); - expect(label).to.be(aggConfig.params.customLabel); - }); - it('default label should be "Count"', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - const label = aggConfig.makeLabel(); - expect(label).to.be('Count'); - }); - it('default label should be "Percentage of Count" when percentageMode is set to true', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - const label = aggConfig.makeLabel(true); - expect(label).to.be('Percentage of Count'); - }); - it('empty label if the visualizationsStart.Vis type is not defined', function() { - const vis = new visualizationsStart.Vis(indexPattern, {}); - const aggConfig = vis.aggs.aggs[0]; - aggConfig.type = undefined; - const label = aggConfig.makeLabel(); - expect(label).to.be(''); - }); - }); - - describe('#fieldFormatter - custom getFormat handler', function() { - it('returns formatter from getFormat handler', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'metric', - aggs: [ - { - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }, - ], - }); - - const fieldFormatter = vis.aggs.aggs[0].fieldFormatter(); - - expect(fieldFormatter).to.be.defined; - expect(fieldFormatter('text')).to.be('text'); - }); - }); - - describe('#fieldFormatter - no custom getFormat handler', function() { - const visStateAggWithoutCustomGetFormat = { - aggs: [ - { - type: 'histogram', - schema: 'bucket', - params: { field: 'bytes' }, - }, - ], - }; - let vis; - - beforeEach(function() { - vis = new visualizationsStart.Vis(indexPattern, visStateAggWithoutCustomGetFormat); - }); - - it("returns the field's formatter", function() { - expect(vis.aggs.aggs[0].fieldFormatter().toString()).to.be( - vis.aggs.aggs[0] - .getField() - .format.getConverterFor() - .toString() - ); - }); - - it('returns the string format if the field does not have a format', function() { - const agg = vis.aggs.aggs[0]; - agg.params.field = { type: 'number', format: null }; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).to.be.defined; - expect(fieldFormatter('text')).to.be('text'); - }); - - it('returns the string format if their is no field', function() { - const agg = vis.aggs.aggs[0]; - delete agg.params.field; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).to.be.defined; - expect(fieldFormatter('text')).to.be('text'); - }); - - it('returns the html converter if "html" is passed in', function() { - const field = indexPattern.fields.getByName('bytes'); - expect(vis.aggs.aggs[0].fieldFormatter('html').toString()).to.be( - field.format.getConverterFor('html').toString() - ); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/__tests__/_agg_configs.js b/src/legacy/ui/public/vis/__tests__/_agg_configs.js deleted file mode 100644 index 172523ec50c8b0..00000000000000 --- a/src/legacy/ui/public/vis/__tests__/_agg_configs.js +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { AggConfig, AggConfigs, AggGroupNames, Schemas } from '../../agg_types'; -import { start as visualizationsStart } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggConfigs', function() { - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - // load main deps - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - }) - ); - - describe('constructor', function() { - it('handles passing just a vis', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [], - }); - - const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); - expect(ac.aggs).to.have.length(1); - }); - - it('converts configStates into AggConfig objects if they are not already', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [], - }); - - const ac = new AggConfigs( - vis.indexPattern, - [ - { - type: 'date_histogram', - schema: 'segment', - }, - new AggConfig(vis.aggs, { - type: 'terms', - schema: 'split', - }), - ], - vis.type.schemas.all - ); - - expect(ac.aggs).to.have.length(3); - }); - - it('attempts to ensure that all states have an id', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [], - }); - - const states = [ - { - type: 'date_histogram', - schema: 'segment', - }, - { - type: 'terms', - schema: 'split', - }, - ]; - - const spy = sinon.spy(AggConfig, 'ensureIds'); - new AggConfigs(vis.indexPattern, states, vis.type.schemas.all); - expect(spy.callCount).to.be(1); - expect(spy.firstCall.args[0]).to.be(states); - AggConfig.ensureIds.restore(); - }); - - describe('defaults', function() { - let vis; - beforeEach(function() { - vis = { - indexPattern: indexPattern, - type: { - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: 'Simple', - min: 1, - max: 2, - defaults: [ - { schema: 'metric', type: 'count' }, - { schema: 'metric', type: 'avg' }, - { schema: 'metric', type: 'sum' }, - ], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: 'Example', - min: 0, - max: 1, - defaults: [ - { schema: 'segment', type: 'terms' }, - { schema: 'segment', type: 'filters' }, - ], - }, - ]), - }, - }; - }); - - it('should only set the number of defaults defined by the max', function() { - const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); - expect(ac.bySchemaName('metric')).to.have.length(2); - }); - - it('should set the defaults defined in the schema when none exist', function() { - const ac = new AggConfigs(vis.indexPattern, [], vis.type.schemas.all); - expect(ac.aggs).to.have.length(3); - }); - - it('should NOT set the defaults defined in the schema when some exist', function() { - const ac = new AggConfigs( - vis.indexPattern, - [{ schema: 'segment', type: 'date_histogram' }], - vis.type.schemas.all - ); - expect(ac.aggs).to.have.length(3); - expect(ac.bySchemaName('segment')[0].type.name).to.equal('date_histogram'); - }); - }); - }); - - describe('#getRequestAggs', function() { - it('performs a stable sort, but moves metrics to the bottom', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'avg', schema: 'metric' }, - { type: 'terms', schema: 'split' }, - { type: 'histogram', schema: 'split' }, - { type: 'sum', schema: 'metric' }, - { type: 'date_histogram', schema: 'segment' }, - { type: 'filters', schema: 'split' }, - { type: 'percentiles', schema: 'metric' }, - ], - }); - - const sorted = vis.aggs.getRequestAggs(); - const aggs = _.indexBy(vis.aggs.aggs, function(agg) { - return agg.type.name; - }); - - expect(sorted.shift()).to.be(aggs.terms); - expect(sorted.shift()).to.be(aggs.histogram); - expect(sorted.shift()).to.be(aggs.date_histogram); - expect(sorted.shift()).to.be(aggs.filters); - expect(sorted.shift()).to.be(aggs.avg); - expect(sorted.shift()).to.be(aggs.sum); - expect(sorted.shift()).to.be(aggs.percentiles); - expect(sorted).to.have.length(0); - }); - }); - - describe('#getResponseAggs', function() { - it('returns all request aggs for basic aggs', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'split' }, - { type: 'date_histogram', schema: 'segment' }, - { type: 'count', schema: 'metric' }, - ], - }); - - const sorted = vis.aggs.getResponseAggs(); - const aggs = _.indexBy(vis.aggs.aggs, function(agg) { - return agg.type.name; - }); - - expect(sorted.shift()).to.be(aggs.terms); - expect(sorted.shift()).to.be(aggs.date_histogram); - expect(sorted.shift()).to.be(aggs.count); - expect(sorted).to.have.length(0); - }); - - it('expands aggs that have multiple responses', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'split' }, - { type: 'date_histogram', schema: 'segment' }, - { type: 'percentiles', schema: 'metric', params: { percents: [1, 2, 3] } }, - ], - }); - - const sorted = vis.aggs.getResponseAggs(); - const aggs = _.indexBy(vis.aggs.aggs, function(agg) { - return agg.type.name; - }); - - expect(sorted.shift()).to.be(aggs.terms); - expect(sorted.shift()).to.be(aggs.date_histogram); - expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 1); - expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 2); - expect(sorted.shift().id).to.be(aggs.percentiles.id + '.' + 3); - expect(sorted).to.have.length(0); - }); - }); - - describe('#toDsl', function() { - it('uses the sorted aggs', function() { - const vis = new visualizationsStart.Vis(indexPattern, { type: 'histogram' }); - sinon.spy(vis.aggs, 'getRequestAggs'); - vis.aggs.toDsl(); - expect(vis.aggs.getRequestAggs).to.have.property('callCount', 1); - }); - - it('calls aggConfig#toDsl() on each aggConfig and compiles the nested output', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'date_histogram', schema: 'segment' }, - { type: 'filters', schema: 'split' }, - ], - }); - - const aggInfos = vis.aggs.aggs.map(function(aggConfig) { - const football = {}; - - sinon.stub(aggConfig, 'toDsl').returns(football); - - return { - id: aggConfig.id, - football: football, - }; - }); - - (function recurse(lvl) { - const info = aggInfos.shift(); - - expect(lvl).to.have.property(info.id); - expect(lvl[info.id]).to.be(info.football); - - if (lvl[info.id].aggs) { - return recurse(lvl[info.id].aggs); - } - })(vis.aggs.toDsl()); - - expect(aggInfos).to.have.length(1); - }); - - it("skips aggs that don't have a dsl representation", function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - params: { field: '@timestamp', interval: '10s' }, - }, - { type: 'count', schema: 'metric' }, - ], - }); - - const dsl = vis.aggs.toDsl(); - const histo = vis.aggs.byName('date_histogram')[0]; - const count = vis.aggs.byName('count')[0]; - - expect(dsl).to.have.property(histo.id); - expect(dsl[histo.id]).to.be.an('object'); - expect(dsl[histo.id]).to.not.have.property('aggs'); - expect(dsl).to.not.have.property(count.id); - }); - - it('writes multiple metric aggregations at the same level', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment', - params: { field: '@timestamp', interval: '10s' }, - }, - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, - { type: 'min', schema: 'metric', params: { field: 'bytes' } }, - { type: 'max', schema: 'metric', params: { field: 'bytes' } }, - ], - }); - - const dsl = vis.aggs.toDsl(); - - const histo = vis.aggs.byName('date_histogram')[0]; - const metrics = vis.aggs.bySchemaGroup('metrics'); - - expect(dsl).to.have.property(histo.id); - expect(dsl[histo.id]).to.be.an('object'); - expect(dsl[histo.id]).to.have.property('aggs'); - - metrics.forEach(function(metric) { - expect(dsl[histo.id].aggs).to.have.property(metric.id); - expect(dsl[histo.id].aggs[metric.id]).to.not.have.property('aggs'); - }); - }); - - it('writes multiple metric aggregations at every level if the vis is hierarchical', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { type: 'terms', schema: 'segment', params: { field: 'ip', orderBy: 1 } }, - { type: 'terms', schema: 'segment', params: { field: 'extension', orderBy: 1 } }, - { id: 1, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, - { type: 'min', schema: 'metric', params: { field: 'bytes' } }, - { type: 'max', schema: 'metric', params: { field: 'bytes' } }, - ], - }); - vis.isHierarchical = _.constant(true); - - const topLevelDsl = vis.aggs.toDsl(vis.isHierarchical()); - const buckets = vis.aggs.bySchemaGroup('buckets'); - const metrics = vis.aggs.bySchemaGroup('metrics'); - - (function checkLevel(dsl) { - const bucket = buckets.shift(); - expect(dsl).to.have.property(bucket.id); - - expect(dsl[bucket.id]).to.be.an('object'); - expect(dsl[bucket.id]).to.have.property('aggs'); - - metrics.forEach(function(metric) { - expect(dsl[bucket.id].aggs).to.have.property(metric.id); - expect(dsl[bucket.id].aggs[metric.id]).to.not.have.property('aggs'); - }); - - if (buckets.length) { - checkLevel(dsl[bucket.id].aggs); - } - })(topLevelDsl); - }); - - it('adds the parent aggs of nested metrics at every level if the vis is hierarchical', function() { - const vis = new visualizationsStart.Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - id: '1', - type: 'avg_bucket', - schema: 'metric', - params: { - customBucket: { - id: '1-bucket', - type: 'date_histogram', - schema: 'bucketAgg', - params: { - field: '@timestamp', - interval: '10s', - }, - }, - customMetric: { - id: '1-metric', - type: 'count', - schema: 'metricAgg', - params: {}, - }, - }, - }, - { - id: '2', - type: 'terms', - schema: 'bucket', - params: { - field: 'geo.src', - }, - }, - { - id: '3', - type: 'terms', - schema: 'bucket', - params: { - field: 'machine.os', - }, - }, - ], - }); - vis.isHierarchical = _.constant(true); - - const topLevelDsl = vis.aggs.toDsl(vis.isHierarchical())['2']; - expect(topLevelDsl.aggs).to.have.keys(['1', '1-bucket']); - expect(topLevelDsl.aggs['1'].avg_bucket).to.have.property('buckets_path', '1-bucket>_count'); - expect(topLevelDsl.aggs['3'].aggs).to.have.keys(['1', '1-bucket']); - expect(topLevelDsl.aggs['3'].aggs['1'].avg_bucket).to.have.property( - 'buckets_path', - '1-bucket>_count' - ); - }); - }); -}); diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts new file mode 100644 index 00000000000000..bc38374e147cf6 --- /dev/null +++ b/src/plugins/data/common/field_formats/mocks.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FieldFormat, IFieldFormatsRegistry } from '.'; + +const fieldFormatMock = ({ + convert: jest.fn(), + getConverterFor: jest.fn(), + getParamDefaults: jest.fn(), + param: jest.fn(), + params: jest.fn(), + toJSON: jest.fn(), + type: jest.fn(), + setupContentType: jest.fn(), +} as unknown) as FieldFormat; + +export const fieldFormatsMock: IFieldFormatsRegistry = { + getByFieldType: jest.fn(), + getDefaultConfig: jest.fn(), + getDefaultInstance: jest.fn().mockImplementation(() => fieldFormatMock) as any, + getDefaultInstanceCacheResolver: jest.fn(), + getDefaultInstancePlain: jest.fn(), + getDefaultType: jest.fn(), + getDefaultTypeName: jest.fn(), + getInstance: jest.fn() as any, + getType: jest.fn(), + getTypeNameByEsTypes: jest.fn(), + init: jest.fn(), + register: jest.fn(), + parseDefaultTypeMap: jest.fn(), + deserialize: jest.fn(), + getTypeWithoutMetaParams: jest.fn(), +}; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 6a0a33096eaac1..27de3b5a29bfd0 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,13 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { - Plugin, - DataPublicPluginSetup, - DataPublicPluginStart, - IndexPatternsContract, - IFieldFormatsRegistry, -} from '.'; + +import { Plugin, DataPublicPluginSetup, DataPublicPluginStart, IndexPatternsContract } from '.'; +import { fieldFormatsMock } from '../common/field_formats/mocks'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -35,24 +31,6 @@ const autocompleteMock: any = { hasQuerySuggestions: jest.fn(), }; -const fieldFormatsMock: IFieldFormatsRegistry = { - getByFieldType: jest.fn(), - getDefaultConfig: jest.fn(), - getDefaultInstance: jest.fn() as any, - getDefaultInstanceCacheResolver: jest.fn(), - getDefaultInstancePlain: jest.fn(), - getDefaultType: jest.fn(), - getDefaultTypeName: jest.fn(), - getInstance: jest.fn() as any, - getType: jest.fn(), - getTypeNameByEsTypes: jest.fn(), - init: jest.fn(), - register: jest.fn(), - parseDefaultTypeMap: jest.fn(), - deserialize: jest.fn(), - getTypeWithoutMetaParams: jest.fn(), -}; - const createSetupContract = (): Setup => { const querySetupMock = queryServiceMock.createSetupContract(); const setupContract = { diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index fd72158012de6f..700bea741bd6ae 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -17,25 +17,6 @@ * under the License. */ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"), you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import { ISearchSource } from './search_source'; export const searchSourceMock: MockedKeys = { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx index ef4b5f6d7b834f..2e1645c816140c 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx @@ -17,10 +17,6 @@ jest.mock('ui/new_platform'); // mock away actual dependencies to prevent all of it being loaded jest.mock('../../../../../../src/legacy/core_plugins/interpreter/public/registries', () => {}); -jest.mock('../../../../../../src/legacy/core_plugins/data/public/legacy', () => ({ - start: {}, - setup: {}, -})); jest.mock('./embeddable/embeddable_factory', () => ({ EmbeddableFactory: class Mock {}, })); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index c883983f8cf010..da04b970a494b8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -2803,7 +2803,7 @@ tr:hover .c3:focus::before {
- - - -
- - -
- - - - + - - -
- - - - - - + - + title="Cu0n232QMyvNtzb75j" + > - - Cu0n232QMyvNtzb75j - - + + - - - - - + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + + - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
+ + + + + + + + +
+ + + + + + + +
+ + +
+ +
@@ -3105,7 +3087,7 @@ tr:hover .c3:focus::before {
- - - -
- - -
- - - - + - - -
- - - - - - + - + title="files" + > - - files - - + + - - - - - + viewBox="0 0 16 16" + width={16} + xmlns="http://www.w3.org/2000/svg" + /> + + - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
+ + + + + + + + +
+ + + + + + + +
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�XQjYjxgHSfU&kein@skwcwquR%t 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