diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 51433f598ac16..b924c7a1a2c29 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -179,6 +179,8 @@ /x-pack/legacy/plugins/rollup/ @elastic/es-ui /x-pack/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui +/x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui +/x-pack/plugins/upgrade_assistant/ @elastic/es-ui /x-pack/plugins/watcher/ @elastic/es-ui # Endpoint diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index c1f06aff722b5..2d4d00b730109 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -231,7 +231,7 @@ This setting does not impact <> and <> visualizations. Supported on {ece}. Each layer object points to an external vector file that contains a geojson FeatureCollection. The file must use the -https://en.wikipedia.org/wiki/World_Geodetic_System[WGS84 coordinate reference system] +https://en.wikipedia.org/wiki/World_Geodetic_System[WGS84 coordinate reference system (ESPG:4326)] and only include polygons. If the file is hosted on a separate domain from Kibana, the server needs to be CORS-enabled so Kibana can download the file. The following example shows a valid regionmap configuration. diff --git a/examples/demo_search/common/index.ts b/examples/demo_search/common/index.ts index 6587ee96ef61b..8ea8d6186ee82 100644 --- a/examples/demo_search/common/index.ts +++ b/examples/demo_search/common/index.ts @@ -20,6 +20,7 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../src/plugins/data/public'; export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY'; +export const ASYNC_DEMO_SEARCH_STRATEGY = 'ASYNC_DEMO_SEARCH_STRATEGY'; export interface IDemoRequest extends IKibanaSearchRequest { mood: string | 'sad' | 'happy'; @@ -29,3 +30,11 @@ export interface IDemoRequest extends IKibanaSearchRequest { export interface IDemoResponse extends IKibanaSearchResponse { greeting: string; } + +export interface IAsyncDemoRequest extends IKibanaSearchRequest { + fibonacciNumbers: number; +} + +export interface IAsyncDemoResponse extends IKibanaSearchResponse { + fibonacciSequence: number[]; +} diff --git a/examples/demo_search/kibana.json b/examples/demo_search/kibana.json index 0603706b07d1f..cb73274ed23f7 100644 --- a/examples/demo_search/kibana.json +++ b/examples/demo_search/kibana.json @@ -2,7 +2,7 @@ "id": "demoSearch", "version": "0.0.1", "kibanaVersion": "kibana", - "configPath": ["demo_search"], + "configPath": ["demoSearch"], "server": true, "ui": true, "requiredPlugins": ["data"], diff --git a/examples/demo_search/public/async_demo_search_strategy.ts b/examples/demo_search/public/async_demo_search_strategy.ts new file mode 100644 index 0000000000000..7a3f33ce05a75 --- /dev/null +++ b/examples/demo_search/public/async_demo_search_strategy.ts @@ -0,0 +1,69 @@ +/* + * 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 } from 'rxjs'; +import { + ISearchContext, + TSearchStrategyProvider, + ISearchStrategy, +} from '../../../src/plugins/data/public'; + +import { ASYNC_DEMO_SEARCH_STRATEGY, IAsyncDemoResponse } from '../common'; +import { ASYNC_SEARCH_STRATEGY } from '../../../x-pack/plugins/data_enhanced/public'; + +/** + * This demo search strategy provider simply provides a shortcut for calling the DEMO_ASYNC_SEARCH_STRATEGY + * on the server side, without users having to pass it in explicitly, and it takes advantage of the + * already registered ASYNC_SEARCH_STRATEGY that exists on the client. + * + * so instead of callers having to do: + * + * ``` + * search( + * { ...request, serverStrategy: DEMO_ASYNC_SEARCH_STRATEGY }, + * options, + * ASYNC_SEARCH_STRATEGY + * ) as Observable, + *``` + + * They can instead just do + * + * ``` + * search(request, options, DEMO_ASYNC_SEARCH_STRATEGY); + * ``` + * + * and are ensured type safety in regard to the request and response objects. + * + * @param context - context supplied by other plugins. + * @param search - a search function to access other strategies that have already been registered. + */ +export const asyncDemoClientSearchStrategyProvider: TSearchStrategyProvider = ( + context: ISearchContext +): ISearchStrategy => { + const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); + const { search } = asyncStrategyProvider(context); + return { + search: (request, options) => { + return search( + { ...request, serverStrategy: ASYNC_DEMO_SEARCH_STRATEGY }, + options + ) as Observable; + }, + }; +}; diff --git a/examples/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts index cb2480c8a5f19..8dc2779a8544c 100644 --- a/examples/demo_search/public/demo_search_strategy.ts +++ b/examples/demo_search/public/demo_search_strategy.ts @@ -43,7 +43,7 @@ import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; * ``` * context.search(request, options, DEMO_SEARCH_STRATEGY); * ``` - * + * * and are ensured type safety in regard to the request and response objects. * * @param context - context supplied by other plugins. diff --git a/examples/demo_search/public/plugin.ts b/examples/demo_search/public/plugin.ts index 62c912716e627..a2539cc7a21c5 100644 --- a/examples/demo_search/public/plugin.ts +++ b/examples/demo_search/public/plugin.ts @@ -19,9 +19,16 @@ import { DataPublicPluginSetup } from '../../../src/plugins/data/public'; import { Plugin, CoreSetup } from '../../../src/core/public'; -import { DEMO_SEARCH_STRATEGY } from '../common'; +import { + DEMO_SEARCH_STRATEGY, + IDemoRequest, + IDemoResponse, + ASYNC_DEMO_SEARCH_STRATEGY, + IAsyncDemoRequest, + IAsyncDemoResponse, +} from '../common'; import { demoClientSearchStrategyProvider } from './demo_search_strategy'; -import { IDemoRequest, IDemoResponse } from '../common'; +import { asyncDemoClientSearchStrategyProvider } from './async_demo_search_strategy'; interface DemoDataSearchSetupDependencies { data: DataPublicPluginSetup; @@ -39,10 +46,12 @@ interface DemoDataSearchSetupDependencies { declare module '../../../src/plugins/data/public' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoRequest; } export interface IResponseTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoResponse; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoResponse; } } @@ -52,6 +61,10 @@ export class DemoDataPlugin implements Plugin { DEMO_SEARCH_STRATEGY, demoClientSearchStrategyProvider ); + deps.data.search.registerSearchStrategyProvider( + ASYNC_DEMO_SEARCH_STRATEGY, + asyncDemoClientSearchStrategyProvider + ); } public start() {} diff --git a/examples/demo_search/server/async_demo_search_strategy.ts b/examples/demo_search/server/async_demo_search_strategy.ts new file mode 100644 index 0000000000000..d2d40891a5d93 --- /dev/null +++ b/examples/demo_search/server/async_demo_search_strategy.ts @@ -0,0 +1,60 @@ +/* + * 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 { TSearchStrategyProvider } from '../../../src/plugins/data/server'; +import { ASYNC_DEMO_SEARCH_STRATEGY } from '../common'; + +function getFibonacciSequence(n = 0) { + const beginning = [0, 1].slice(0, n); + return Array(Math.max(0, n)) + .fill(null) + .reduce((sequence, value, i) => { + if (i < 2) return sequence; + return [...sequence, sequence[i - 1] + sequence[i - 2]]; + }, beginning); +} + +const generateId = (() => { + let id = 0; + return () => `${id++}`; +})(); + +const loadedMap = new Map(); +const totalMap = new Map(); + +export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider = () => { + return { + search: async request => { + const id = request.id ?? generateId(); + + const loaded = (loadedMap.get(id) ?? 0) + 1; + loadedMap.set(id, loaded); + + const total = request.fibonacciNumbers ?? totalMap.get(id); + totalMap.set(id, total); + + const fibonacciSequence = getFibonacciSequence(loaded); + return { id, total, loaded, fibonacciSequence }; + }, + cancel: async id => { + loadedMap.delete(id); + totalMap.delete(id); + }, + }; +}; diff --git a/examples/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts index 653aa217717fa..49fbae43e3aa2 100644 --- a/examples/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -20,7 +20,15 @@ import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server'; import { demoSearchStrategyProvider } from './demo_search_strategy'; -import { DEMO_SEARCH_STRATEGY, IDemoRequest, IDemoResponse } from '../common'; +import { + DEMO_SEARCH_STRATEGY, + IDemoRequest, + IDemoResponse, + ASYNC_DEMO_SEARCH_STRATEGY, + IAsyncDemoRequest, + IAsyncDemoResponse, +} from '../common'; +import { asyncDemoSearchStrategyProvider } from './async_demo_search_strategy'; interface IDemoSearchExplorerDeps { data: DataPluginSetup; @@ -38,10 +46,12 @@ interface IDemoSearchExplorerDeps { declare module '../../../src/plugins/data/server' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoRequest; } export interface IResponseTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoResponse; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoResponse; } } @@ -54,6 +64,11 @@ export class DemoDataPlugin implements Plugin id: 'demoSearch', component: , }, + { + title: 'Async demo search strategy', + id: 'asyncDemoSearch', + component: , + }, ]; const routes = pages.map((page, i) => ( diff --git a/examples/search_explorer/public/async_demo_strategy.tsx b/examples/search_explorer/public/async_demo_strategy.tsx new file mode 100644 index 0000000000000..40ddcc1f48fe7 --- /dev/null +++ b/examples/search_explorer/public/async_demo_strategy.tsx @@ -0,0 +1,123 @@ +/* + * 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 { + EuiPageContentBody, + EuiFormRow, + EuiFlexItem, + EuiFlexGroup, + EuiFieldNumber, +} from '@elastic/eui'; +import { ISearchGeneric } from '../../../src/plugins/data/public'; +import { DoSearch } from './do_search'; +import { GuideSection } from './guide_section'; + +import { ASYNC_DEMO_SEARCH_STRATEGY, IAsyncDemoRequest } from '../../demo_search/common'; + +// @ts-ignore +import demoStrategyServerProvider from '!!raw-loader!./../../demo_search/server/async_demo_search_strategy'; +// @ts-ignore +import demoStrategyPublicProvider from '!!raw-loader!./../../demo_search/public/async_demo_search_strategy'; +// @ts-ignore +import demoStrategyServerPlugin from '!!raw-loader!./../../demo_search/server/plugin'; +// @ts-ignore +import demoStrategyPublicPlugin from '!!raw-loader!./../../demo_search/public/plugin'; + +interface Props { + search: ISearchGeneric; +} + +interface State { + searching: boolean; + fibonacciNumbers: number; + changes: boolean; + error?: any; +} + +export class AsyncDemoStrategy extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + searching: false, + changes: false, + fibonacciNumbers: 5, + }; + } + + renderDemo = () => { + const request: IAsyncDemoRequest = { + fibonacciNumbers: this.state.fibonacciNumbers, + }; + return ( + + + + + this.setState({ fibonacciNumbers: parseFloat(e.target.value) })} + /> + + + + + this.props.search(request, { signal }, ASYNC_DEMO_SEARCH_STRATEGY) + } + /> + + ); + }; + + render() { + return ( + + + + ); + } +} diff --git a/examples/search_explorer/public/do_search.tsx b/examples/search_explorer/public/do_search.tsx index f279b9fcd6e23..a6b6b9b57db4a 100644 --- a/examples/search_explorer/public/do_search.tsx +++ b/examples/search_explorer/public/do_search.tsx @@ -118,8 +118,8 @@ ${requestStr} Response: ({ + getChanges: async () => + new Map([ + ['/foo/bar/a', 'modified'], + ['/foo/bar/b', 'modified'], + ['/foo/bar/c', 'deleted'], + ]), +})); + +jest.mock('./get_mtimes.ts', () => ({ + getMtimes: async (paths: string[]) => new Map(paths.map(path => [path, 12345])), +})); + +jest.mock('execa'); + +jest.mock('fs', () => { + const realFs = jest.requireActual('fs'); + return { + ...realFs, + readFile: jest.fn(realFs.readFile), + }; +}); + +expect.addSnapshotSerializer(createAbsolutePathSerializer()); + +jest.requireMock('execa').mockImplementation(async (cmd: string, args: string[], opts: object) => { + expect(cmd).toBe('git'); + expect(args).toEqual([ + 'log', + '-n', + '1', + '--pretty=format:%H', + '--', + expect.stringContaining('kbn-optimizer'), + ]); + expect(opts).toEqual({ + cwd: REPO_ROOT, + }); + + return { + stdout: '', + }; +}); + +describe('getOptimizerCacheKey()', () => { + it('uses latest commit, bootstrap cache, and changed files to create unique value', async () => { + jest + .requireMock('fs') + .readFile.mockImplementation( + (path: string, enc: string, cb: (err: null, file: string) => void) => { + expect(path).toBe( + Path.resolve(REPO_ROOT, 'packages/kbn-optimizer/target/.bootstrap-cache') + ); + expect(enc).toBe('utf8'); + cb(null, ''); + } + ); + + const config = OptimizerConfig.create({ + repoRoot: REPO_ROOT, + }); + + await expect(getOptimizerCacheKey(config)).resolves.toMatchInlineSnapshot(` + Object { + "bootstrap": "", + "deletedPaths": Array [ + "/foo/bar/c", + ], + "lastCommit": "", + "modifiedTimes": Object { + "/foo/bar/a": 12345, + "/foo/bar/b": 12345, + }, + "workerConfig": Object { + "browserslistEnv": "dev", + "cache": true, + "dist": false, + "optimizerCacheKey": "♻", + "profileWebpack": false, + "repoRoot": , + "watch": false, + }, + } + `); + }); +}); + +describe('diffCacheKey()', () => { + it('returns undefined if values are equal', () => { + expect(diffCacheKey('1', '1')).toBe(undefined); + expect(diffCacheKey(1, 1)).toBe(undefined); + expect(diffCacheKey(['1', '2', { a: 'b' }], ['1', '2', { a: 'b' }])).toBe(undefined); + expect( + diffCacheKey( + { + a: '1', + b: '2', + }, + { + b: '2', + a: '1', + } + ) + ).toBe(undefined); + }); + + it('returns a diff if the values are different', () => { + expect(diffCacheKey(['1', '2', { a: 'b' }], ['1', '2', { b: 'a' }])).toMatchInlineSnapshot(` + "- Expected + + Received + +  Array [ +  \\"1\\", +  \\"2\\", +  Object { + - \\"a\\": \\"b\\", + + \\"b\\": \\"a\\", +  }, +  ]" + `); + expect( + diffCacheKey( + { + a: '1', + b: '1', + }, + { + b: '2', + a: '2', + } + ) + ).toMatchInlineSnapshot(` + "- Expected + + Received + +  Object { + - \\"a\\": \\"1\\", + - \\"b\\": \\"1\\", + + \\"a\\": \\"2\\", + + \\"b\\": \\"2\\", +  }" + `); + }); +}); + +describe('reformatJestDiff()', () => { + it('reformats large jestDiff output to focus on the changed lines', () => { + const diff = jestDiff( + { + a: ['1', '1', '1', '1', '1', '1', '1', '2', '1', '1', '1', '1', '1', '1', '1', '1', '1'], + }, + { + b: ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '1', '1', '1', '1'], + } + ); + + expect(reformatJestDiff(diff)).toMatchInlineSnapshot(` + "- Expected + + Received + +  Object { + - \\"a\\": Array [ + + \\"b\\": Array [ +  \\"1\\", +  \\"1\\", +  ... +  \\"1\\", +  \\"1\\", + - \\"2\\", +  \\"1\\", +  \\"1\\", +  ... +  \\"1\\", +  \\"1\\", + + \\"2\\", +  \\"1\\", +  \\"1\\", +  ..." + `); + }); +}); diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index ce46f534141f4..8cde5d0a1fc11 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -54,6 +54,7 @@ export * from '../common'; export { FilterStateManager } from './filter/filter_manager'; export { // agg_types TODO need to group these under a namespace or prefix + AggConfigs, AggParamType, AggTypeFilters, // TODO convert to interface aggTypeFilters, @@ -66,6 +67,7 @@ export { convertIPRangeToString, intervalOptions, // only used in Discover isDateHistogramBucketAggConfig, + setBounds, isStringType, isType, isValidInterval, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 096877d5824c4..cfd12b3283459 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -36,6 +36,7 @@ import { VisualizationsStart } from '../../../visualizations/public'; import { SavedVisualizations } from './np_ready/types'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { KibanaLegacyStart } from '../../../../../plugins/kibana_legacy/public'; +import { DefaultEditorController } from '../../../vis_default_editor/public'; export interface VisualizeKibanaServices { pluginInitializerContext: PluginInitializerContext; @@ -60,6 +61,7 @@ export interface VisualizeKibanaServices { usageCollection?: UsageCollectionSetup; I18nContext: I18nStart['Context']; setActiveUrl: (newUrl: string) => void; + DefaultVisualizationEditor: typeof DefaultEditorController; } let services: VisualizeKibanaServices | null = null; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 8b1bb0fda8c84..d9565938c838d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -43,8 +43,7 @@ export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; export { wrapInI18nContext } from 'ui/i18n'; export { DashboardConstants } from '../dashboard/np_ready/dashboard_constants'; -export { VisSavedObject } from '../../../visualizations/public/embeddable/visualize_embeddable'; -export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; +export { VisSavedObject, VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/'; export { configureAppAngularModule, ensureDefaultIndexPattern, diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js index c40a10115ae4e..65c25b8cf705d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js @@ -30,7 +30,7 @@ export function initVisEditorDirective(app, deps) { appState: '=', }, link: function($scope, element) { - const Editor = $scope.savedObj.vis.type.editor; + const Editor = $scope.savedObj.vis.type.editor || deps.DefaultVisualizationEditor; const editor = new Editor(element[0], $scope.savedObj); $scope.renderFunction = () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 22804685db3cc..9f2283d29c203 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -50,6 +50,7 @@ import { HomePublicPluginSetup, } from '../../../../../plugins/home/public'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; +import { DefaultEditorController } from '../../../vis_default_editor/public'; export interface VisualizePluginStartDependencies { data: DataPublicPluginStart; @@ -139,7 +140,7 @@ export class VisualizePlugin implements Plugin { localStorage: new Storage(localStorage), navigation, savedObjectsClient, - savedVisualizations: visualizations.getSavedVisualizationsLoader(), + savedVisualizations: visualizations.savedVisualizationsLoader, savedQueryService: dataStart.query.savedQueries, share, toastNotifications: coreStart.notifications.toasts, @@ -150,6 +151,7 @@ export class VisualizePlugin implements Plugin { usageCollection, I18nContext: coreStart.i18n.Context, setActiveUrl, + DefaultVisualizationEditor: DefaultEditorController, }; setServices(deps); diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx b/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx index 32ea71c0bc005..7eee54006f684 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx @@ -20,8 +20,10 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; import { EditorRenderProps } from '../../kibana/public/visualize/np_ready/types'; -import { VisualizeEmbeddable } from '../../visualizations/public/embeddable'; -import { VisualizeEmbeddableFactory } from '../../visualizations/public/embeddable/visualize_embeddable_factory'; +import { + VisualizeEmbeddableContract as VisualizeEmbeddable, + VisualizeEmbeddableFactoryContract as VisualizeEmbeddableFactory, +} from '../../visualizations/public/'; import { PanelsContainer, Panel } from '../../../../plugins/kibana_react/public'; import './vis_type_agg_filter'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx index d3090d277aef9..db910604eddd1 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types'; -import { VisSavedObject } from 'src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable'; +import { VisSavedObject } from 'src/legacy/core_plugins/visualizations/public/'; import { Storage } from '../../../../plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; import { DefaultEditor } from './default_editor'; diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts b/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts deleted file mode 100644 index f37bc858efab0..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts +++ /dev/null @@ -1,103 +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 { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; -import { toastNotifications } from 'ui/notify'; - -import { IAggConfig } from 'ui/agg_types'; -import { timefilter } from 'ui/timefilter'; -import { Vis } from '../np_ready/public'; -import { Filter, Query, SearchSource, ISearchSource } from '../../../../../plugins/data/public'; - -interface QueryGeohashBoundsParams { - filters?: Filter[]; - query?: Query; - searchSource?: ISearchSource; -} - -/** - * Coordinate map visualization needs to be able to query for the latest geohash - * bounds when a user clicks the "fit to data" map icon, which requires knowing - * about global filters & queries. This logic has been extracted here so we can - * keep `searchSource` out of the vis, but ultimately we need to design a - * long-term solution for situations like this. - * - * TODO: Remove this as a part of elastic/kibana#30593 - */ -export async function queryGeohashBounds(vis: Vis, params: QueryGeohashBoundsParams) { - const agg = vis.getAggConfig().aggs.find((a: IAggConfig) => { - return get(a, 'type.dslName') === 'geohash_grid'; - }); - - if (agg) { - const searchSource = params.searchSource - ? params.searchSource.createChild() - : new SearchSource(); - searchSource.setField('size', 0); - searchSource.setField('aggs', () => { - const geoBoundsAgg = vis.getAggConfig().createAggConfig( - { - type: 'geo_bounds', - enabled: true, - params: { - field: agg.getField(), - }, - schema: 'metric', - }, - { - addToAggConfigs: false, - } - ); - return { - '1': geoBoundsAgg.toDsl(), - }; - }); - - const { filters, query } = params; - if (filters) { - searchSource.setField('filter', () => { - const activeFilters = [...filters]; - const indexPattern = agg.getIndexPattern(); - const useTimeFilter = !!indexPattern.timeFieldName; - if (useTimeFilter) { - const filter = timefilter.createFilter(indexPattern); - if (filter) activeFilters.push((filter as any) as Filter); - } - return activeFilters; - }); - } - if (query) { - searchSource.setField('query', query); - } - - try { - const esResp = await searchSource.fetch(); - return get(esResp, 'aggregations.1.bounds'); - } catch (error) { - toastNotifications.addDanger({ - title: i18n.translate('visualizations.queryGeohashBounds.unableToGetBoundErrorTitle', { - defaultMessage: 'Unable to get bounds', - }), - text: `${error.message}`, - }); - return; - } - } -} diff --git a/src/legacy/core_plugins/visualizations/public/index.scss b/src/legacy/core_plugins/visualizations/public/index.scss index 748945eabd331..238f58fbfa295 100644 --- a/src/legacy/core_plugins/visualizations/public/index.scss +++ b/src/legacy/core_plugins/visualizations/public/index.scss @@ -1,4 +1,2 @@ @import 'src/legacy/ui/public/styles/styling_constants'; - -@import './embeddable/index'; @import './np_ready/public/index'; diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index 5cff588d951b0..223c130df3505 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -24,9 +24,5 @@ export { IAggConfigs, isDateHistogramBucketAggConfig, setBounds, -} from '../../../ui/public/agg_types'; -export { createFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; -export { I18nContext } from '../../../ui/public/i18n'; -import chrome from '../../../ui/public/chrome'; -export { chrome as legacyChrome }; -import '../../../ui/public/directives/bind'; +} from '../../data/public'; +export { createSavedSearchesLoader } from '../../kibana/public/discover/saved_searches/'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json b/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json index 888edde44a261..d4f9bd327d6ac 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json +++ b/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json @@ -3,8 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": [ - "data", - "search" - ] + "requiredPlugins": ["data", "search", "expressions", "uiActions"] } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/_index.scss b/src/legacy/core_plugins/visualizations/public/np_ready/public/_index.scss index d87b6b004a511..eada763b63c4d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/_index.scss +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/_index.scss @@ -1 +1,2 @@ @import 'wizard/index'; +@import 'embeddable/index'; diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/_embeddables.scss b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_embeddables.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/embeddable/_embeddables.scss rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_embeddables.scss diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/_index.scss b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_index.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/embeddable/_index.scss rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_index.scss diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_visualize_lab_disabled.scss similarity index 100% rename from src/legacy/core_plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/_visualize_lab_disabled.scss diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/constants.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/constants.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/embeddable/constants.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/constants.ts diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_embeddable.tsx similarity index 94% rename from src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_embeddable.tsx index f9dfd5d2b98f4..fbb2eba3afe79 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_embeddable.tsx @@ -19,7 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Embeddable, EmbeddableOutput } from '../../../../../plugins/embeddable/public'; +import { Embeddable, EmbeddableOutput } from '../../../../../../../plugins/embeddable/public'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_visualization.tsx similarity index 100% rename from src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/disabled_lab_visualization.tsx diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts similarity index 90% rename from src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts index cfb2960cfbb7c..51d839275fd27 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/get_index_pattern.ts @@ -17,13 +17,13 @@ * under the License. */ -import { VisSavedObject } from './visualize_embeddable'; +import { VisSavedObject } from '../types'; import { indexPatterns, IIndexPattern, IndexPatternAttributes, -} from '../../../../../plugins/data/public'; -import { getUISettings, getSavedObjects } from '../np_ready/public/services'; +} from '../../../../../../../plugins/data/public'; +import { getUISettings, getSavedObjects } from '../services'; export async function getIndexPattern( savedVis: VisSavedObject diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/index.ts similarity index 92% rename from src/legacy/core_plugins/visualizations/public/embeddable/index.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/index.ts index d7c0205891ec5..a1cd31eebef20 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/index.ts @@ -18,4 +18,5 @@ */ export { DisabledLabEmbeddable } from './disabled_lab_embeddable'; export { VisualizeEmbeddable, VisualizeInput } from './visualize_embeddable'; +export { VisualizeEmbeddableFactory } from './visualize_embeddable_factory'; export { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts similarity index 90% rename from src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index 32bbae13b79b8..2537caa01cd46 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -18,13 +18,8 @@ */ import _, { get } from 'lodash'; -import { PersistedState } from 'ui/persisted_state'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; -import { buildPipeline } from 'ui/visualize/loader/pipeline_helpers'; -import { npStart } from 'ui/new_platform'; -import { IExpressionLoaderParams } from 'src/plugins/expressions/public'; -import { EmbeddableVisTriggerContext } from 'src/plugins/embeddable/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { IIndexPattern, @@ -32,9 +27,8 @@ import { Query, esFilters, Filter, - ISearchSource, TimefilterContract, -} from '../../../../../plugins/data/public'; +} from '../../../../../../../plugins/data/public'; import { EmbeddableInput, EmbeddableOutput, @@ -42,27 +36,21 @@ import { Container, selectRangeTrigger, valueClickTrigger, -} from '../../../../../plugins/embeddable/public'; -import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; -import { SavedObject } from '../../../../../plugins/saved_objects/public'; -import { SavedSearch } from '../../../kibana/public/discover/np_ready/types'; -import { Vis } from '../np_ready/public'; + EmbeddableVisTriggerContext, +} from '../../../../../../../plugins/embeddable/public'; +import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; +import { + IExpressionLoaderParams, + ExpressionsStart, +} from '../../../../../../../plugins/expressions/public'; +import { buildPipeline } from '../legacy/build_pipeline'; +import { Vis } from '../vis'; +import { getExpressions, getUiActions } from '../services'; +import { PersistedState } from '../../../legacy_imports'; +import { VisSavedObject } from '../types'; const getKeys = (o: T): Array => Object.keys(o) as Array; -export interface VisSavedObject extends SavedObject { - vis: Vis; - description?: string; - searchSource: ISearchSource; - title: string; - uiStateJSON?: string; - destroy: () => void; - savedSearchRefName?: string; - savedSearchId?: string; - savedSearch?: SavedSearch; - visState: any; -} - export interface VisualizeEmbeddableConfiguration { savedVisualization: VisSavedObject; indexPatterns?: IIndexPattern[]; @@ -90,7 +78,7 @@ export interface VisualizeOutput extends EmbeddableOutput { visTypeName: string; } -type ExpressionLoader = InstanceType; +type ExpressionLoader = InstanceType; export class VisualizeEmbeddable extends Embeddable { private handler?: ExpressionLoader; @@ -281,7 +269,8 @@ export class VisualizeEmbeddable extends Embeddable { @@ -309,7 +298,9 @@ export class VisualizeEmbeddable extends Embeddable { public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - constructor( - private timefilter: TimefilterContract, - private getSavedVisualizationsLoader: () => SavedVisualizations - ) { + constructor() { super({ savedObjectMetaData: { name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }), @@ -101,7 +98,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const savedVisualizations = this.getSavedVisualizationsLoader(); + const savedVisualizations = getSavedVisualizationsLoader(); try { const visId = savedObject.id as string; @@ -118,7 +115,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const indexPattern = await getIndexPattern(savedObject); const indexPatterns = indexPattern ? [indexPattern] : []; return new VisualizeEmbeddable( - this.timefilter, + getTimeFilter(), { savedVisualization: savedObject, indexPatterns, @@ -141,7 +138,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const savedVisualizations = this.getSavedVisualizationsLoader(); + const savedVisualizations = getSavedVisualizationsLoader(); try { const savedObject = await savedVisualizations.get(savedObjectId); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts index f1c1677a60f26..4ac0931c5d865 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts @@ -19,8 +19,11 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { VisResponseValue } from 'src/plugins/visualizations/public'; -import { ExpressionFunctionDefinition, Render } from 'src/plugins/expressions/public'; +import { VisResponseValue } from '../../../../../../../plugins/visualizations/public'; +import { + ExpressionFunctionDefinition, + Render, +} from '../../../../../../../plugins/expressions/public'; import { PersistedState } from '../../../legacy_imports'; import { getTypes, getIndexPatterns, getFilterManager } from '../services'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts index 3c4a1c1449d47..34ffb698e5f8c 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts @@ -29,22 +29,28 @@ * either types, or static code. */ -import { PluginInitializerContext } from 'src/core/public'; +import { PublicContract } from '@kbn/utility-types'; +import { PluginInitializerContext } from '../../../../../../core/public'; import { VisualizationsPlugin, VisualizationsSetup, VisualizationsStart } from './plugin'; /** @public */ export { VisualizationsSetup, VisualizationsStart }; /** @public types */ -export { VisTypeAlias, VisType } from './types'; +export { VisTypeAlias, VisType } from './vis_types'; +export { VisSavedObject } from './types'; +export { Vis, VisParams, VisState } from './vis'; +import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable'; +export type VisualizeEmbeddableFactoryContract = PublicContract; +export type VisualizeEmbeddableContract = PublicContract; export function plugin(initializerContext: PluginInitializerContext) { return new VisualizationsPlugin(initializerContext); } /** @public static code */ -export { Vis, VisParams, VisState } from './vis'; -export { TypesService } from './types/types_service'; +export { TypesService } from './vis_types/types_service'; +export { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from './embeddable'; export { Status } from './legacy/update_status'; export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/build_pipeline'; @@ -52,4 +58,4 @@ export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/bui // @ts-ignore export { updateOldState } from './legacy/vis_update_state'; export { calculateObjectHash } from './legacy/calculate_object_hash'; -export { createSavedVisLoader } from '../../saved_visualizations/saved_visualizations'; +export { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts index 41b23b276e88d..57c686b6e9cb0 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts @@ -17,12 +17,12 @@ * under the License. */ -import { PluginInitializerContext } from 'src/core/public'; - /* eslint-disable @kbn/eslint/no-restricted-paths */ import { npSetup, npStart } from 'ui/new_platform'; /* eslint-enable @kbn/eslint/no-restricted-paths */ +import { PluginInitializerContext } from '../../../../../../core/public'; + import { plugin } from '.'; const pluginInstance = plugin({} as PluginInitializerContext); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js index b8aa33d0a5abe..9c1dfd9780255 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { BaseVisType } from '../../../types/base_vis_type'; +import { BaseVisType } from '../../../vis_types/base_vis_type'; describe('Base Vis Type', function() { beforeEach(ngMock.module('kibana')); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js index c85557ea1b0b0..2474a58870424 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { ReactVisType } from '../../../types/react_vis_type'; +import { ReactVisType } from '../../../vis_types/react_vis_type'; describe('React Vis Type', function() { const visConfig = { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts index f73dc3e19d0ef..1adf6fd23f5a5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts @@ -31,11 +31,6 @@ import { IAggConfig } from '../../../legacy_imports'; import { searchSourceMock } from '../../../legacy_mocks'; jest.mock('ui/new_platform'); -jest.mock('ui/agg_types', () => ({ - setBounds: () => {}, - dateHistogramBucketAgg: () => {}, - isDateHistogramBucketAggConfig: () => true, -})); describe('visualize loader pipeline helpers: build pipeline', () => { describe('prepareJson', () => { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index 025eef834ca86..155213b4103b0 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -18,17 +18,11 @@ */ import { cloneDeep, get } from 'lodash'; -// @ts-ignore import moment from 'moment'; -import { SerializedFieldFormat } from 'src/plugins/expressions/public'; -import { ISearchSource } from 'src/plugins/data/public'; -import { - IAggConfig, - setBounds, - isDateHistogramBucketAggConfig, - createFormat, -} from '../../../legacy_imports'; -import { Vis, VisParams } from '..'; +import { SerializedFieldFormat } from '../../../../../../../plugins/expressions/public'; +import { fieldFormats, ISearchSource } from '../../../../../../../plugins/data/public'; +import { IAggConfig, setBounds, isDateHistogramBucketAggConfig } from '../../../legacy_imports'; +import { Vis, VisParams } from '../types'; interface SchemaConfigParams { precision?: number; @@ -102,7 +96,7 @@ export const getSchemas = (vis: Vis, timeRange?: any): Schemas => { 'max_bucket', ].includes(agg.type.name); - const format = createFormat( + const format = fieldFormats.serialize( hasSubAgg ? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg) : agg diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 9fb87cadb2983..b3dd22f62f81f 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -23,7 +23,7 @@ jest.mock('ui/vis/vis_factory'); jest.mock('ui/registry/vis_types'); jest.mock('./types/vis_type_alias_registry'); -import { PluginInitializerContext } from 'src/core/public'; +import { PluginInitializerContext } from '../../../../../../core/public'; import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; import { coreMock } from '../../../../../../core/public/mocks'; @@ -31,6 +31,7 @@ import { embeddablePluginMock } from '../../../../../../plugins/embeddable/publi import { expressionsPluginMock } from '../../../../../../plugins/expressions/public/mocks'; import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../plugins/usage_collection/public/mocks'; +import { uiActionsPluginMock } from '../../../../../../plugins/ui_actions/public/mocks'; const createSetupContract = (): VisualizationsSetup => ({ types: { @@ -47,7 +48,7 @@ const createStartContract = (): VisualizationsStart => ({ all: jest.fn(), getAliases: jest.fn(), }, - getSavedVisualizationsLoader: jest.fn(), + savedVisualizationsLoader: {} as any, showNewVisModal: jest.fn(), Vis: jest.fn(), }); @@ -64,6 +65,8 @@ const createInstance = async () => { const doStart = () => plugin.start(coreMock.createStart(), { data: dataPluginMock.createStartContract(), + expressions: expressionsPluginMock.createStartContract(), + uiActions: uiActionsPluginMock.createStartContract(), }); return { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index 20bed59faad88..e1d87d414d398 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -17,8 +17,13 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { TypesService, TypesSetup, TypesStart } from './types'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, +} from '../../../../../../core/public'; +import { TypesService, TypesSetup, TypesStart } from './vis_types'; import { setUISettings, setTypes, @@ -29,10 +34,13 @@ import { setSavedObjects, setUsageCollector, setFilterManager, + setExpressions, + setUiActions, + setSavedVisualizationsLoader, + setTimeFilter, } from './services'; -import { VisualizeEmbeddableFactory } from '../../embeddable/visualize_embeddable_factory'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../embeddable'; -import { ExpressionsSetup } from '../../../../../../plugins/expressions/public'; +import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable'; +import { ExpressionsSetup, ExpressionsStart } from '../../../../../../plugins/expressions/public'; import { IEmbeddableSetup } from '../../../../../../plugins/embeddable/public'; import { visualization as visualizationFunction } from './expressions/visualization_function'; import { visualization as visualizationRenderer } from './expressions/visualization_renderer'; @@ -41,13 +49,11 @@ import { DataPublicPluginStart, } from '../../../../../../plugins/data/public'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; -import { - createSavedVisLoader, - SavedObjectKibanaServicesWithVisualizations, -} from '../../saved_visualizations'; -import { SavedVisualizations } from '../../../../kibana/public/visualize/np_ready/types'; +import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations'; import { VisImpl, VisImplConstructor } from './vis_impl'; import { showNewVisModal } from './wizard'; +import { UiActionsStart } from '../../../../../../plugins/ui_actions/public'; + /** * Interface for this plugin's returned setup/start contracts. * @@ -59,9 +65,9 @@ export interface VisualizationsSetup { export interface VisualizationsStart { types: TypesStart; - getSavedVisualizationsLoader: () => SavedVisualizations; - showNewVisModal: typeof showNewVisModal; + savedVisualizationsLoader: SavedVisualizationsLoader; Vis: VisImplConstructor; + showNewVisModal: typeof showNewVisModal; } export interface VisualizationsSetupDeps { @@ -73,6 +79,8 @@ export interface VisualizationsSetupDeps { export interface VisualizationsStartDeps { data: DataPublicPluginStart; + expressions: ExpressionsStart; + uiActions: UiActionsStart; } /** @@ -92,8 +100,6 @@ export class VisualizationsPlugin VisualizationsStartDeps > { private readonly types: TypesService = new TypesService(); - private savedVisualizations?: SavedVisualizations; - private savedVisualizationDependencies?: SavedObjectKibanaServicesWithVisualizations; constructor(initializerContext: PluginInitializerContext) {} @@ -107,10 +113,7 @@ export class VisualizationsPlugin expressions.registerFunction(visualizationFunction); expressions.registerRenderer(visualizationRenderer); - const embeddableFactory = new VisualizeEmbeddableFactory( - data.query.timefilter.timefilter, - this.getSavedVisualizationsLoader - ); + const embeddableFactory = new VisualizeEmbeddableFactory(); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); return { @@ -118,7 +121,10 @@ export class VisualizationsPlugin }; } - public start(core: CoreStart, { data }: VisualizationsStartDeps): VisualizationsStart { + public start( + core: CoreStart, + { data, expressions, uiActions }: VisualizationsStartDeps + ): VisualizationsStart { const types = this.types.start(); setI18n(core.i18n); setTypes(types); @@ -127,31 +133,27 @@ export class VisualizationsPlugin setSavedObjects(core.savedObjects); setIndexPatterns(data.indexPatterns); setFilterManager(data.query.filterManager); - - this.savedVisualizationDependencies = { + setExpressions(expressions); + setUiActions(uiActions); + setTimeFilter(data.query.timefilter.timefilter); + const savedVisualizationsLoader = createSavedVisLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns: data.indexPatterns, chrome: core.chrome, overlays: core.overlays, visualizationTypes: types, - }; + }); + setSavedVisualizationsLoader(savedVisualizationsLoader); return { types, - getSavedVisualizationsLoader: () => this.getSavedVisualizationsLoader(), showNewVisModal, Vis: VisImpl, + savedVisualizationsLoader, }; } public stop() { this.types.stop(); } - - private getSavedVisualizationsLoader = () => { - if (!this.savedVisualizations) { - this.savedVisualizations = createSavedVisLoader(this.savedVisualizationDependencies!); - } - return this.savedVisualizations; - }; } diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts similarity index 90% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/_saved_vis.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts index f4548da375216..f3539b3564c56 100644 --- a/src/legacy/core_plugins/visualizations/public/saved_visualizations/_saved_vis.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts @@ -28,15 +28,13 @@ import { createSavedObjectClass, SavedObject, SavedObjectKibanaServices, -} from '../../../../../plugins/saved_objects/public'; -import { updateOldState } from '../index'; +} from '../../../../../../../plugins/saved_objects/public'; +import { updateOldState } from '../../../index'; import { extractReferences, injectReferences } from './saved_visualization_references'; -import { IIndexPattern } from '../../../../../plugins/data/public'; -import { VisSavedObject } from '../embeddable/visualize_embeddable'; - -import { createSavedSearchesLoader } from '../../../kibana/public/discover'; -import { VisualizeConstants } from '../../../kibana/public/visualize'; -import { VisImpl } from '../np_ready/public/vis_impl'; +import { IIndexPattern } from '../../../../../../../plugins/data/public'; +import { VisSavedObject } from '../types'; +import { VisImpl } from '../vis_impl'; +import { createSavedSearchesLoader } from '../../../legacy_imports'; async function _afterEsResp(savedVis: VisSavedObject, services: any) { await _getLinkedSavedSearch(savedVis, services); @@ -138,7 +136,7 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) { }); this.showInRecentlyAccessed = true; this.getFullPath = () => { - return `/app/kibana#${VisualizeConstants.EDIT_PATH}/${this.id}`; + return `/app/kibana#/visualize/edit/${this.id}`; }; } } diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/find_list_items.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.test.ts similarity index 77% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/find_list_items.test.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.test.ts index ed0f6dc429ef4..d1def09978dbb 100644 --- a/src/legacy/core_plugins/visualizations/public/saved_visualizations/find_list_items.test.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.test.ts @@ -18,19 +18,24 @@ */ import { findListItems } from './find_list_items'; +import { coreMock } from '../../../../../../../core/public/mocks'; +import { SavedObjectsClientContract } from '../../../../../../../core/public'; +import { VisTypeAlias } from '../vis_types'; describe('saved_visualizations', () => { function testProps() { + const savedObjects = coreMock.createStart().savedObjects.client as jest.Mocked< + SavedObjectsClientContract + >; + (savedObjects.find as jest.Mock).mockImplementation(() => ({ + total: 0, + savedObjects: [], + })); return { visTypes: [], search: '', size: 10, - savedObjectsClient: { - find: jest.fn(async () => ({ - total: 0, - savedObjects: [], - })), - }, + savedObjectsClient: savedObjects, mapSavedObjectApiHits: jest.fn(), }; } @@ -60,7 +65,7 @@ describe('saved_visualizations', () => { searchFields: ['baz', 'bing'], }, }, - }, + } as VisTypeAlias, ], }; const { find } = props.savedObjectsClient; @@ -86,7 +91,7 @@ describe('saved_visualizations', () => { searchFields: ['baz', 'bing', 'barfield'], }, }, - }, + } as VisTypeAlias, { appExtensions: { visualizations: { @@ -94,7 +99,7 @@ describe('saved_visualizations', () => { searchFields: ['baz', 'bing', 'foofield'], }, }, - }, + } as VisTypeAlias, ], }; const { find } = props.savedObjectsClient; @@ -128,24 +133,11 @@ describe('saved_visualizations', () => { it('uses type-specific toListItem function, if available', async () => { const props = { ...testProps(), - savedObjectsClient: { - find: jest.fn(async () => ({ - total: 2, - savedObjects: [ - { - id: 'lotr', - type: 'wizard', - attributes: { label: 'Gandalf' }, - }, - { - id: 'wat', - type: 'visualization', - attributes: { title: 'WATEVER' }, - }, - ], - })), - }, - mapSavedObjectApiHits(savedObject) { + mapSavedObjectApiHits(savedObject: { + id: string; + type: string; + attributes: { title: string }; + }) { return { id: savedObject.id, title: `DEFAULT ${savedObject.attributes.title}`, @@ -159,14 +151,31 @@ describe('saved_visualizations', () => { toListItem(savedObject) { return { id: savedObject.id, - title: `${savedObject.attributes.label} THE GRAY`, + title: `${(savedObject.attributes as { label: string }).label} THE GRAY`, }; }, }, }, - }, + } as VisTypeAlias, ], }; + + (props.savedObjectsClient.find as jest.Mock).mockImplementationOnce(async () => ({ + total: 2, + savedObjects: [ + { + id: 'lotr', + type: 'wizard', + attributes: { label: 'Gandalf' }, + }, + { + id: 'wat', + type: 'visualization', + attributes: { title: 'WATEVER' }, + }, + ], + })); + const items = await findListItems(props); expect(items).toEqual({ total: 2, diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/find_list_items.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.ts similarity index 64% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/find_list_items.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.ts index a7fcee67adf72..02db90a762e89 100644 --- a/src/legacy/core_plugins/visualizations/public/saved_visualizations/find_list_items.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/find_list_items.ts @@ -18,6 +18,13 @@ */ import _ from 'lodash'; +import { + SavedObjectAttributes, + SavedObjectsClientContract, +} from '../../../../../../../core/public'; +import { SavedObjectLoader } from '../../../../../../../plugins/saved_objects/public'; +import { VisTypeAlias } from '../vis_types'; +import { VisualizationsAppExtension } from '../vis_types/vis_type_alias_registry'; /** * Search for visualizations and convert them into a list display-friendly format. @@ -28,34 +35,42 @@ export async function findListItems({ size, savedObjectsClient, mapSavedObjectApiHits, +}: { + search: string; + size: number; + visTypes: VisTypeAlias[]; + savedObjectsClient: SavedObjectsClientContract; + mapSavedObjectApiHits: SavedObjectLoader['mapSavedObjectApiHits']; }) { - const extensions = _.compact( - visTypes.map(v => v.appExtensions && v.appExtensions.visualizations) - ); + const extensions = visTypes + .map(v => v.appExtensions?.visualizations) + .filter(Boolean) as VisualizationsAppExtension[]; const extensionByType = extensions.reduce((acc, m) => { - return m.docTypes.reduce((_acc, type) => { + return m!.docTypes.reduce((_acc, type) => { acc[type] = m; return acc; }, acc); - }, {}); - const searchOption = (field, ...defaults) => + }, {} as { [visType: string]: VisualizationsAppExtension }); + const searchOption = (field: string, ...defaults: string[]) => _(extensions) .pluck(field) .concat(defaults) .compact() .flatten() .uniq() - .value(); + .value() as string[]; const searchOptions = { type: searchOption('docTypes', 'visualization'), searchFields: searchOption('searchFields', 'title^3', 'description'), search: search ? `${search}*` : undefined, perPage: size, page: 1, - defaultSearchOperator: 'AND', + defaultSearchOperator: 'AND' as 'AND', }; - const { total, savedObjects } = await savedObjectsClient.find(searchOptions); + const { total, savedObjects } = await savedObjectsClient.find( + searchOptions + ); return { total, diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/index.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts similarity index 96% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts index 6549b317d1634..98af6d99025c2 100644 --- a/src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.test.ts @@ -18,7 +18,7 @@ */ import { extractReferences, injectReferences } from './saved_visualization_references'; -import { VisSavedObject } from '../embeddable/visualize_embeddable'; +import { VisSavedObject, VisState } from '../types'; describe('extractReferences', () => { test('extracts nothing if savedSearchId is empty', () => { @@ -128,7 +128,7 @@ Object { id: '1', title: 'test', savedSearchRefName: 'search_0', - visState: { + visState: ({ params: { controls: [ { @@ -140,7 +140,7 @@ Object { }, ], }, - }, + } as unknown) as VisState, } as VisSavedObject; const references = [ { @@ -192,7 +192,7 @@ Object { const context = { id: '1', title: 'test', - visState: { + visState: ({ params: { controls: [ { @@ -201,7 +201,7 @@ Object { }, ], }, - }, + } as unknown) as VisState, } as VisSavedObject; expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( `"Could not find index pattern reference \\"control_0_index_pattern\\""` diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.ts similarity index 95% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.ts index 330f5e2dacd10..b995d340d44d9 100644 --- a/src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualization_references.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { SavedObjectAttributes, SavedObjectReference } from 'kibana/public'; -import { VisSavedObject } from '../embeddable/visualize_embeddable'; +import { SavedObjectAttributes, SavedObjectReference } from '../../../../../../../core/public'; +import { VisSavedObject } from '../types'; export function extractReferences({ attributes, diff --git a/src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualizations.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualizations.ts similarity index 91% rename from src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualizations.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualizations.ts index 7d0d6a10ff66f..fc0f77d54059c 100644 --- a/src/legacy/core_plugins/visualizations/public/saved_visualizations/saved_visualizations.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/saved_visualizations.ts @@ -19,18 +19,15 @@ import { SavedObjectLoader, SavedObjectKibanaServices, -} from '../../../../../plugins/saved_objects/public'; - -// @ts-ignore +} from '../../../../../../../plugins/saved_objects/public'; import { findListItems } from './find_list_items'; import { createSavedVisClass } from './_saved_vis'; -import { createVisualizeEditUrl } from '../../../kibana/public/visualize'; -import { TypesStart } from '../np_ready/public/types'; +import { TypesStart } from '../vis_types'; export interface SavedObjectKibanaServicesWithVisualizations extends SavedObjectKibanaServices { visualizationTypes: TypesStart; } - +export type SavedVisualizationsLoader = ReturnType; export function createSavedVisLoader(services: SavedObjectKibanaServicesWithVisualizations) { const { savedObjectsClient, visualizationTypes } = services; @@ -59,7 +56,7 @@ export function createSavedVisLoader(services: SavedObjectKibanaServicesWithVisu source.icon = source.type.icon; source.image = source.type.image; source.typeTitle = source.type.title; - source.editUrl = `#${createVisualizeEditUrl(id)}`; + source.editUrl = `#/visualize/edit/${id}`; return source; }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts index 433c5c7b6df0d..a977a4b452bf7 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts @@ -23,11 +23,18 @@ import { I18nStart, IUiSettingsClient, SavedObjectsStart, -} from 'src/core/public'; -import { TypesStart } from './types'; +} from '../../../../../../core/public'; +import { TypesStart } from './vis_types'; import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; -import { FilterManager, IndexPatternsContract } from '../../../../../../plugins/data/public'; +import { + FilterManager, + IndexPatternsContract, + TimefilterContract, +} from '../../../../../../plugins/data/public'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; +import { ExpressionsStart } from '../../../../../../plugins/expressions/public'; +import { UiActionsStart } from '../../../../../../plugins/ui_actions/public'; +import { SavedVisualizationsLoader } from './saved_visualizations'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -47,6 +54,8 @@ export const [getFilterManager, setFilterManager] = createGetterSetter('TimeFilter'); + export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( 'IndexPatterns' ); @@ -54,3 +63,11 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( 'UsageCollection' ); + +export const [getExpressions, setExpressions] = createGetterSetter('Expressions'); + +export const [getUiActions, setUiActions] = createGetterSetter('UiActions'); + +export const [getSavedVisualizationsLoader, setSavedVisualizationsLoader] = createGetterSetter< + SavedVisualizationsLoader +>('SavedVisualisationsLoader'); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/types.ts new file mode 100644 index 0000000000000..d2ca4ffb92eb2 --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/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 { SavedObject } from '../../../../../../plugins/saved_objects/public'; +import { Vis, VisState, VisParams, VisualizationController } from './vis'; +import { ISearchSource } from '../../../../../../plugins/data/public/'; +import { SavedSearch } from '../../../../kibana/public/discover/np_ready/types'; + +export { Vis, VisState, VisParams, VisualizationController }; + +export interface VisSavedObject extends SavedObject { + vis: Vis; + description?: string; + searchSource: ISearchSource; + title: string; + uiStateJSON?: string; + destroy: () => void; + savedSearchRefName?: string; + savedSearchId?: string; + savedSearch?: SavedSearch; + visState: VisState; +} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts index 19375e25a9fb7..990f27dca7556 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisType } from './types'; +import { VisType } from './vis_types'; import { IAggConfigs } from '../../legacy_imports'; import { Status } from './legacy/update_status'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts index f9b7db5c02d93..62b68082e21f8 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.d.ts @@ -18,7 +18,7 @@ */ import { Vis, VisState, VisParams } from './vis'; -import { VisType } from './types'; +import { VisType } from './vis_types'; import { IIndexPattern } from '../../../../../../plugins/data/common'; type InitVisStateType = diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/base_vis_type.js similarity index 95% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/base_vis_type.js index 351acc48e2676..50ff74cfe9dd3 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/base_vis_type.js @@ -19,8 +19,6 @@ import _ from 'lodash'; -import { DefaultEditorController } from '../../../../../vis_default_editor/public'; - export class BaseVisType { constructor(opts = {}) { if (!opts.name) { @@ -47,7 +45,7 @@ export class BaseVisType { }, requestHandler: 'courier', // select one from registry or pass a function responseHandler: 'none', - editor: DefaultEditorController, + editor: null, // no default is provided editorConfig: { collections: {}, // collections used for configuration (list of positions, ...) }, diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/index.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/index.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/index.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/react_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/react_vis_type.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/react_vis_type.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/vis_type_alias_registry.ts similarity index 97% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/vis_type_alias_registry.ts index 97f4798c296d4..12b02ee9e6b32 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/vis_type_alias_registry.ts @@ -27,7 +27,7 @@ interface VisualizationListItem { typeTitle: string; } -interface VisualizationsAppExtension { +export interface VisualizationsAppExtension { docTypes: string[]; searchFields?: string[]; toListItem: (savedObject: { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx index 58f7bc49d1cdb..2712019e42609 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.test.tsx @@ -19,9 +19,9 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { TypesStart, VisType } from '../types'; +import { TypesStart, VisType } from '../vis_types'; import { NewVisModal } from './new_vis_modal'; -import { SavedObjectsStart } from 'kibana/public'; +import { SavedObjectsStart } from '../../../../../../../core/public'; describe('NewVisModal', () => { const { location } = window; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx index b39e8e8926707..7c10001eddb50 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/new_vis_modal.tsx @@ -23,10 +23,10 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; -import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; +import { IUiSettingsClient, SavedObjectsStart } from '../../../../../../../core/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; -import { TypesStart, VisType, VisTypeAlias } from '../types'; +import { TypesStart, VisType, VisTypeAlias } from '../vis_types'; import { UsageCollectionSetup } from '../../../../../../../plugins/usage_collection/public'; interface TypeSelectionProps { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx index 9b3b8a6425e52..f8eb191dd5f92 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/search_selection/search_selection.tsx @@ -21,10 +21,10 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui' import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; +import { IUiSettingsClient, SavedObjectsStart } from '../../../../../../../../core/public'; import { SavedObjectFinderUi } from '../../../../../../../../plugins/saved_objects/public'; -import { VisType } from '../../types'; +import { VisType } from '../../vis_types'; interface SearchSelectionProps { onSearchSelected: (searchId: string, searchType: string) => void; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.tsx index 5068f43952c4e..e84314853ba4c 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/new_vis_help.tsx @@ -21,7 +21,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; import { VisTypeAliasListEntry } from './type_selection'; -import { VisTypeAlias } from '../../types'; +import { VisTypeAlias } from '../../vis_types'; interface Props { promotedTypes: VisTypeAliasListEntry[]; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx index 574f5b3cccc99..81dcecfee2613 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/type_selection/type_selection.tsx @@ -40,7 +40,7 @@ import { VisTypeAlias } from '../../../../../../visualizations/public'; import { NewVisHelp } from './new_vis_help'; import { VisHelpText } from './vis_help_text'; import { VisTypeIcon } from './vis_type_icon'; -import { VisType, TypesStart } from '../../types'; +import { VisType, TypesStart } from '../../vis_types'; export interface VisTypeListEntry extends VisType { highlighted: boolean; diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index e1fe7d414756a..7600bd9db6094 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -23,12 +23,6 @@ export interface IKibanaSearchResponse { */ id?: string; - /** - * If relevant to the search strategy, return a percentage - * that represents how progress is indicated. - */ - percentComplete?: number; - /** * If relevant to the search strategy, return a total number * that represents how progress is indicated. diff --git a/src/plugins/data/public/search/es_search/es_search_strategy.ts b/src/plugins/data/public/search/es_search/es_search_strategy.ts index a5eab20a89f53..5382a59123e78 100644 --- a/src/plugins/data/public/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/public/search/es_search/es_search_strategy.ts @@ -26,6 +26,8 @@ import { ISearchContext, TSearchStrategyProvider, ISearchStrategy } from '../typ export const esSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext ): ISearchStrategy => { + const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); + const { search } = syncStrategyProvider(context); return { search: (request, options) => { if (typeof request.params.preference === 'undefined') { @@ -33,11 +35,9 @@ export const esSearchStrategyProvider: TSearchStrategyProvider; + return search({ ...request, serverStrategy: ES_SEARCH_STRATEGY }, options) as Observable< + IEsSearchResponse + >; }, }; }; diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 762c278a05640..853dbd09e1f93 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -35,7 +35,7 @@ export { export { IEsSearchResponse, IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../common/search'; -export { SYNC_SEARCH_STRATEGY } from './sync_search_strategy'; +export { ISyncSearchRequest, SYNC_SEARCH_STRATEGY } from './sync_search_strategy'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; diff --git a/src/plugins/data/public/search/sync_search_strategy.test.ts b/src/plugins/data/public/search/sync_search_strategy.test.ts index 9378e5833f8d7..31a1adfa01c75 100644 --- a/src/plugins/data/public/search/sync_search_strategy.test.ts +++ b/src/plugins/data/public/search/sync_search_strategy.test.ts @@ -35,12 +35,9 @@ describe('Sync search strategy', () => { core: mockCoreStart, getSearchStrategy: jest.fn(), }); - syncSearch.search( - { - serverStrategy: SYNC_SEARCH_STRATEGY, - }, - {} - ); + const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; + syncSearch.search(request, {}); + expect(mockCoreStart.http.fetch.mock.calls[0][0]).toEqual({ path: `/internal/search/${SYNC_SEARCH_STRATEGY}`, body: JSON.stringify({ @@ -50,4 +47,47 @@ describe('Sync search strategy', () => { signal: undefined, }); }); + + it('increments and decrements loading count on success', async () => { + const expectedLoadingCountValues = [0, 1, 0]; + const receivedLoadingCountValues: number[] = []; + + mockCoreStart.http.fetch.mockResolvedValueOnce('response'); + + const syncSearch = syncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn(), + }); + const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; + + const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; + loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); + + await syncSearch.search(request, {}).toPromise(); + + expect(receivedLoadingCountValues).toEqual(expectedLoadingCountValues); + }); + + it('increments and decrements loading count on failure', async () => { + expect.assertions(1); + const expectedLoadingCountValues = [0, 1, 0]; + const receivedLoadingCountValues: number[] = []; + + mockCoreStart.http.fetch.mockRejectedValueOnce('error'); + + const syncSearch = syncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn(), + }); + const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; + + const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; + loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); + + try { + await syncSearch.search(request, {}).toPromise(); + } catch (e) { + expect(receivedLoadingCountValues).toEqual(expectedLoadingCountValues); + } + }); }); diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts index c2cc180af546e..860ce593ae217 100644 --- a/src/plugins/data/public/search/sync_search_strategy.ts +++ b/src/plugins/data/public/search/sync_search_strategy.ts @@ -18,7 +18,8 @@ */ import { BehaviorSubject, from } from 'rxjs'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../common/search'; +import { finalize } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '../../common/search'; import { ISearch, ISearchOptions } from './i_search'; import { TSearchStrategyProvider, ISearchStrategy, ISearchContext } from './types'; @@ -40,16 +41,14 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider { loadingCount$.next(loadingCount$.getValue() + 1); - const response: Promise = context.core.http.fetch({ - path: `/internal/search/${request.serverStrategy}`, - method: 'POST', - body: JSON.stringify(request), - signal: options.signal, - }); - - response.then(() => loadingCount$.next(loadingCount$.getValue() - 1)); - - return from(response); + return from( + context.core.http.fetch({ + path: `/internal/search/${request.serverStrategy}`, + method: 'POST', + body: JSON.stringify(request), + signal: options.signal, + }) + ).pipe(finalize(() => loadingCount$.next(loadingCount$.getValue() - 1))); }; return { search }; diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index f032890e98901..02a5e0921fe4f 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -23,6 +23,7 @@ import { IRouter } from 'kibana/server'; import { IFieldType, Filter } from '../index'; import { findIndexPatternById, getFieldByName } from '../index_patterns'; +import { getRequestAbortedSignal } from '../lib'; export function registerValueSuggestionsRoute(router: IRouter) { router.post( @@ -50,6 +51,7 @@ export function registerValueSuggestionsRoute(router: IRouter) { const { field: fieldName, query, boolFilter } = request.body; const { index } = request.params; const { dataClient } = context.core.elasticsearch; + const signal = getRequestAbortedSignal(request.events.aborted$); const autocompleteSearchOptions = { timeout: await uiSettings.get('kibana.autocompleteTimeout'), @@ -62,7 +64,7 @@ export function registerValueSuggestionsRoute(router: IRouter) { const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter); try { - const result = await dataClient.callAsCurrentUser('search', { index, body }); + const result = await dataClient.callAsCurrentUser('search', { index, body }, { signal }); const buckets: any[] = get(result, 'aggregations.suggestions.buckets') || diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts new file mode 100644 index 0000000000000..3c1e20dbcb158 --- /dev/null +++ b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts @@ -0,0 +1,45 @@ +/* + * 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 { Subject } from 'rxjs'; +import { getRequestAbortedSignal } from './get_request_aborted_signal'; + +describe('abortableRequestHandler', () => { + jest.useFakeTimers(); + + it('should call abort if disconnected', () => { + const abortedSubject = new Subject(); + const aborted$ = abortedSubject.asObservable(); + const onAborted = jest.fn(); + + const signal = getRequestAbortedSignal(aborted$); + signal.addEventListener('abort', onAborted); + + // Shouldn't be aborted or call onAborted prior to disconnecting + expect(signal.aborted).toBe(false); + expect(onAborted).not.toBeCalled(); + + abortedSubject.next(); + jest.runAllTimers(); + + // Should be aborted and call onAborted after disconnecting + expect(signal.aborted).toBe(true); + expect(onAborted).toBeCalled(); + }); +}); diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.ts b/src/plugins/data/server/lib/get_request_aborted_signal.ts new file mode 100644 index 0000000000000..d1541f1df9384 --- /dev/null +++ b/src/plugins/data/server/lib/get_request_aborted_signal.ts @@ -0,0 +1,33 @@ +/* + * 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 } from 'rxjs'; +// @ts-ignore not typed +import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill'; + +/** + * A simple utility function that returns an `AbortSignal` corresponding to an `AbortController` + * which aborts when the given request is aborted. + * @param aborted$ The observable of abort events (usually `request.events.aborted$`) + */ +export function getRequestAbortedSignal(aborted$: Observable): AbortSignal { + const controller = new AbortController(); + aborted$.subscribe(() => controller.abort()); + return controller.signal; +} diff --git a/src/plugins/data/server/lib/index.ts b/src/plugins/data/server/lib/index.ts new file mode 100644 index 0000000000000..a2af456846e14 --- /dev/null +++ b/src/plugins/data/server/lib/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { getRequestAbortedSignal } from './get_request_aborted_signal'; diff --git a/src/plugins/data/server/search/create_api.test.ts b/src/plugins/data/server/search/create_api.test.ts index cc13269e1aa21..99e48056ef857 100644 --- a/src/plugins/data/server/search/create_api.test.ts +++ b/src/plugins/data/server/search/create_api.test.ts @@ -25,7 +25,7 @@ import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; // let mockCoreSetup: MockedKeys; -const mockDefaultSearch = jest.fn(() => Promise.resolve({ percentComplete: 0 })); +const mockDefaultSearch = jest.fn(() => Promise.resolve({ total: 100, loaded: 0 })); const mockDefaultSearchStrategyProvider = jest.fn(() => Promise.resolve({ search: mockDefaultSearch, diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index e1613103ac399..798a4b82caaef 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -31,7 +31,7 @@ export function createApi({ }) { const api: IRouteHandlerSearchContext = { search: async (request, options, strategyName) => { - const name = strategyName ? strategyName : DEFAULT_SEARCH_STRATEGY; + const name = strategyName ?? DEFAULT_SEARCH_STRATEGY; const strategyProvider = searchStrategies[name]; if (!strategyProvider) { throw new Error(`No strategy found for ${strategyName}`); @@ -40,6 +40,15 @@ export function createApi({ const strategy = await strategyProvider(caller, api.search); return strategy.search(request, options); }, + cancel: async (id, strategyName) => { + const name = strategyName ?? DEFAULT_SEARCH_STRATEGY; + const strategyProvider = searchStrategies[name]; + if (!strategyProvider) { + throw new Error(`No strategy found for ${strategyName}`); + } + const strategy = await strategyProvider(caller, api.search); + return strategy.cancel && strategy.cancel(id); + }, }; return api; } diff --git a/src/plugins/data/server/search/i_route_handler_search_context.ts b/src/plugins/data/server/search/i_route_handler_search_context.ts index 8a44738a1dcfa..89862781b826e 100644 --- a/src/plugins/data/server/search/i_route_handler_search_context.ts +++ b/src/plugins/data/server/search/i_route_handler_search_context.ts @@ -17,8 +17,9 @@ * under the License. */ -import { ISearchGeneric } from './i_search'; +import { ISearchGeneric, ICancelGeneric } from './i_search'; export interface IRouteHandlerSearchContext { search: ISearchGeneric; + cancel: ICancelGeneric; } diff --git a/src/plugins/data/server/search/i_search.ts b/src/plugins/data/server/search/i_search.ts index 0a35734574153..ea014c5e136d9 100644 --- a/src/plugins/data/server/search/i_search.ts +++ b/src/plugins/data/server/search/i_search.ts @@ -42,7 +42,14 @@ export type ISearchGeneric = Promise; +export type ICancelGeneric = ( + id: string, + strategy?: T +) => Promise; + export type ISearch = ( request: IRequestTypesMap[T], options?: ISearchOptions ) => Promise; + +export type ICancel = (id: string) => Promise; diff --git a/src/plugins/data/server/search/i_search_strategy.ts b/src/plugins/data/server/search/i_search_strategy.ts index d00dd552c9e95..4cfc9608383a9 100644 --- a/src/plugins/data/server/search/i_search_strategy.ts +++ b/src/plugins/data/server/search/i_search_strategy.ts @@ -18,7 +18,7 @@ */ import { APICaller } from 'kibana/server'; -import { ISearch, ISearchGeneric } from './i_search'; +import { ISearch, ICancel, ISearchGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; import { ISearchContext } from './i_search_context'; @@ -28,6 +28,7 @@ import { ISearchContext } from './i_search_context'; */ export interface ISearchStrategy { search: ISearch; + cancel?: ICancel; } /** diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 6f726771c41b2..2b8c4b95ee022 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -19,6 +19,7 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../../../core/server'; +import { getRequestAbortedSignal } from '../lib'; export function registerSearchRoute(router: IRouter): void { router.post( @@ -34,9 +35,11 @@ export function registerSearchRoute(router: IRouter): void { }, async (context, request, res) => { const searchRequest = request.body; - const strategy = request.params.strategy; + const { strategy } = request.params; + const signal = getRequestAbortedSignal(request.events.aborted$); + try { - const response = await context.search!.search(searchRequest, {}, strategy); + const response = await context.search!.search(searchRequest, { signal }, strategy); return res.ok({ body: response }); } catch (err) { return res.customError({ @@ -51,4 +54,35 @@ export function registerSearchRoute(router: IRouter): void { } } ); + + router.delete( + { + path: '/internal/search/{strategy}/{id}', + validate: { + params: schema.object({ + strategy: schema.string(), + id: schema.string(), + }), + + query: schema.object({}, { allowUnknowns: true }), + }, + }, + async (context, request, res) => { + const { strategy, id } = request.params; + try { + await context.search!.cancel(id, strategy); + return res.ok(); + } catch (err) { + return res.customError({ + statusCode: err.statusCode, + body: { + message: err.message, + attributes: { + error: err.body.error, + }, + }, + }); + } + } + ); } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 534e1cae3f62d..a1b332bb65617 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -22,6 +22,7 @@ import { Adapters } from '../types'; import { IContainer } from '../containers'; import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; import { ViewMode } from '../types'; +import { EmbeddableActionStorage } from './embeddable_action_storage'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; @@ -49,6 +50,11 @@ export abstract class Embeddable< // TODO: Rename to destroyed. private destoyed: boolean = false; + private __actionStorage?: EmbeddableActionStorage; + public get actionStorage(): EmbeddableActionStorage { + return this.__actionStorage || (this.__actionStorage = new EmbeddableActionStorage(this)); + } + constructor(input: TEmbeddableInput, output: TEmbeddableOutput, parent?: IContainer) { this.id = input.id; this.output = { diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts new file mode 100644 index 0000000000000..56facc37fc666 --- /dev/null +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts @@ -0,0 +1,536 @@ +/* + * 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 { Embeddable } from './embeddable'; +import { EmbeddableInput } from './i_embeddable'; +import { ViewMode } from '../types'; +import { EmbeddableActionStorage, SerializedEvent } from './embeddable_action_storage'; +import { of } from '../../../../kibana_utils/common'; + +class TestEmbeddable extends Embeddable { + public readonly type = 'test'; + constructor() { + super({ id: 'test', viewMode: ViewMode.VIEW }, {}); + } + reload() {} +} + +describe('EmbeddableActionStorage', () => { + describe('.create()', () => { + test('method exists', () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + expect(typeof storage.create).toBe('function'); + }); + + test('can add event to embeddable', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + const events1 = embeddable.getInput().events || []; + expect(events1).toEqual([]); + + await storage.create(event); + + const events2 = embeddable.getInput().events || []; + expect(events2).toEqual([event]); + }); + + test('can create multiple events', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + const event2: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + const event3: SerializedEvent = { + eventId: 'EVENT_ID3', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + const events1 = embeddable.getInput().events || []; + expect(events1).toEqual([]); + + await storage.create(event1); + + const events2 = embeddable.getInput().events || []; + expect(events2).toEqual([event1]); + + await storage.create(event2); + await storage.create(event3); + + const events3 = embeddable.getInput().events || []; + expect(events3).toEqual([event1, event2, event3]); + }); + + test('throws when creating an event with the same ID', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + await storage.create(event); + const [, error] = await of(storage.create(event)); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[EEXIST]: Event with [eventId = EVENT_ID] already exists on [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + }); + + describe('.update()', () => { + test('method exists', () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + expect(typeof storage.update).toBe('function'); + }); + + test('can update an existing event', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: { + name: 'foo', + } as any, + }; + const event2: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: { + name: 'bar', + } as any, + }; + + await storage.create(event1); + await storage.update(event2); + + const events = embeddable.getInput().events || []; + expect(events).toEqual([event2]); + }); + + test('updates event in place of the old event', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID', + action: { + name: 'foo', + } as any, + }; + const event2: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID', + action: { + name: 'bar', + } as any, + }; + const event22: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID', + action: { + name: 'baz', + } as any, + }; + const event3: SerializedEvent = { + eventId: 'EVENT_ID3', + triggerId: 'TRIGGER-ID', + action: { + name: 'qux', + } as any, + }; + + await storage.create(event1); + await storage.create(event2); + await storage.create(event3); + + const events1 = embeddable.getInput().events || []; + expect(events1).toEqual([event1, event2, event3]); + + await storage.update(event22); + + const events2 = embeddable.getInput().events || []; + expect(events2).toEqual([event1, event22, event3]); + + await storage.update(event2); + + const events3 = embeddable.getInput().events || []; + expect(events3).toEqual([event1, event2, event3]); + }); + + test('throws when updating event, but storage is empty', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + const [, error] = await of(storage.update(event)); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[ENOENT]: Event with [eventId = EVENT_ID] could not be updated as it does not exist in [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + + test('throws when updating event with ID that is not stored', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + const event2: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + await storage.create(event1); + const [, error] = await of(storage.update(event2)); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[ENOENT]: Event with [eventId = EVENT_ID2] could not be updated as it does not exist in [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + }); + + describe('.remove()', () => { + test('method exists', () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + expect(typeof storage.remove).toBe('function'); + }); + + test('can remove existing event', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + await storage.create(event); + await storage.remove(event.eventId); + + const events = embeddable.getInput().events || []; + expect(events).toEqual([]); + }); + + test('removes correct events in a list of events', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID', + action: { + name: 'foo', + } as any, + }; + const event2: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID', + action: { + name: 'bar', + } as any, + }; + const event3: SerializedEvent = { + eventId: 'EVENT_ID3', + triggerId: 'TRIGGER-ID', + action: { + name: 'qux', + } as any, + }; + + await storage.create(event1); + await storage.create(event2); + await storage.create(event3); + + const events1 = embeddable.getInput().events || []; + expect(events1).toEqual([event1, event2, event3]); + + await storage.remove(event2.eventId); + + const events2 = embeddable.getInput().events || []; + expect(events2).toEqual([event1, event3]); + + await storage.remove(event3.eventId); + + const events3 = embeddable.getInput().events || []; + expect(events3).toEqual([event1]); + + await storage.remove(event1.eventId); + + const events4 = embeddable.getInput().events || []; + expect(events4).toEqual([]); + }); + + test('throws when removing an event from an empty storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const [, error] = await of(storage.remove('EVENT_ID')); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[ENOENT]: Event with [eventId = EVENT_ID] could not be removed as it does not exist in [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + + test('throws when removing with ID that does not exist in storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + await storage.create(event); + const [, error] = await of(storage.remove('WRONG_ID')); + await storage.remove(event.eventId); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[ENOENT]: Event with [eventId = WRONG_ID] could not be removed as it does not exist in [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + }); + + describe('.read()', () => { + test('method exists', () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + expect(typeof storage.read).toBe('function'); + }); + + test('can read an existing event out of storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + await storage.create(event); + const event2 = await storage.read(event.eventId); + + expect(event2).toEqual(event); + }); + + test('throws when reading from empty storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const [, error] = await of(storage.read('EVENT_ID')); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[ENOENT]: Event with [eventId = EVENT_ID] could not be found in [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + + test('throws when reading event with ID not existing in storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event: SerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + await storage.create(event); + const [, error] = await of(storage.read('WRONG_ID')); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatchInlineSnapshot( + `"[ENOENT]: Event with [eventId = WRONG_ID] could not be found in [embeddable.id = test, embeddable.title = undefined]."` + ); + }); + + test('returns correct event when multiple events are stored', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID1', + action: {} as any, + }; + const event2: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID2', + action: {} as any, + }; + const event3: SerializedEvent = { + eventId: 'EVENT_ID3', + triggerId: 'TRIGGER-ID3', + action: {} as any, + }; + + await storage.create(event1); + await storage.create(event2); + await storage.create(event3); + + const event12 = await storage.read(event1.eventId); + const event22 = await storage.read(event2.eventId); + const event32 = await storage.read(event3.eventId); + + expect(event12).toEqual(event1); + expect(event22).toEqual(event2); + expect(event32).toEqual(event3); + + expect(event12).not.toEqual(event2); + }); + }); + + describe('.count()', () => { + test('method exists', () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + expect(typeof storage.count).toBe('function'); + }); + + test('returns 0 when storage is empty', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const count = await storage.count(); + + expect(count).toBe(0); + }); + + test('returns correct number of events in storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + expect(await storage.count()).toBe(0); + + await storage.create({ + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID1', + action: {} as any, + }); + + expect(await storage.count()).toBe(1); + + await storage.create({ + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID1', + action: {} as any, + }); + + expect(await storage.count()).toBe(2); + + await storage.remove('EVENT_ID1'); + + expect(await storage.count()).toBe(1); + + await storage.remove('EVENT_ID2'); + + expect(await storage.count()).toBe(0); + }); + }); + + describe('.list()', () => { + test('method exists', () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + expect(typeof storage.list).toBe('function'); + }); + + test('returns empty array when storage is empty', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const list = await storage.list(); + + expect(list).toEqual([]); + }); + + test('returns correct list of events in storage', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + + const event1: SerializedEvent = { + eventId: 'EVENT_ID1', + triggerId: 'TRIGGER-ID1', + action: {} as any, + }; + + const event2: SerializedEvent = { + eventId: 'EVENT_ID2', + triggerId: 'TRIGGER-ID1', + action: {} as any, + }; + + expect(await storage.list()).toEqual([]); + + await storage.create(event1); + + expect(await storage.list()).toEqual([event1]); + + await storage.create(event2); + + expect(await storage.list()).toEqual([event1, event2]); + + await storage.remove('EVENT_ID1'); + + expect(await storage.list()).toEqual([event2]); + + await storage.remove('EVENT_ID2'); + + expect(await storage.list()).toEqual([]); + }); + }); +}); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts new file mode 100644 index 0000000000000..520f92840c5f9 --- /dev/null +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts @@ -0,0 +1,126 @@ +/* + * 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 { Embeddable } from '..'; + +/** + * Below two interfaces are here temporarily, they will move to `ui_actions` + * plugin once #58216 is merged. + */ +export interface SerializedEvent { + eventId: string; + triggerId: string; + action: unknown; +} +export interface ActionStorage { + create(event: SerializedEvent): Promise; + update(event: SerializedEvent): Promise; + remove(eventId: string): Promise; + read(eventId: string): Promise; + count(): Promise; + list(): Promise; +} + +export class EmbeddableActionStorage implements ActionStorage { + constructor(private readonly embbeddable: Embeddable) {} + + async create(event: SerializedEvent) { + const input = this.embbeddable.getInput(); + const events = (input.events || []) as SerializedEvent[]; + const exists = !!events.find(({ eventId }) => eventId === event.eventId); + + if (exists) { + throw new Error( + `[EEXIST]: Event with [eventId = ${event.eventId}] already exists on ` + + `[embeddable.id = ${input.id}, embeddable.title = ${input.title}].` + ); + } + + this.embbeddable.updateInput({ + ...input, + events: [...events, event], + }); + } + + async update(event: SerializedEvent) { + const input = this.embbeddable.getInput(); + const events = (input.events || []) as SerializedEvent[]; + const index = events.findIndex(({ eventId }) => eventId === event.eventId); + + if (index === -1) { + throw new Error( + `[ENOENT]: Event with [eventId = ${event.eventId}] could not be ` + + `updated as it does not exist in ` + + `[embeddable.id = ${input.id}, embeddable.title = ${input.title}].` + ); + } + + this.embbeddable.updateInput({ + ...input, + events: [...events.slice(0, index), event, ...events.slice(index + 1)], + }); + } + + async remove(eventId: string) { + const input = this.embbeddable.getInput(); + const events = (input.events || []) as SerializedEvent[]; + const index = events.findIndex(event => eventId === event.eventId); + + if (index === -1) { + throw new Error( + `[ENOENT]: Event with [eventId = ${eventId}] could not be ` + + `removed as it does not exist in ` + + `[embeddable.id = ${input.id}, embeddable.title = ${input.title}].` + ); + } + + this.embbeddable.updateInput({ + ...input, + events: [...events.slice(0, index), ...events.slice(index + 1)], + }); + } + + async read(eventId: string): Promise { + const input = this.embbeddable.getInput(); + const events = (input.events || []) as SerializedEvent[]; + const event = events.find(ev => eventId === ev.eventId); + + if (!event) { + throw new Error( + `[ENOENT]: Event with [eventId = ${eventId}] could not be found in ` + + `[embeddable.id = ${input.id}, embeddable.title = ${input.title}].` + ); + } + + return event; + } + + private __list() { + const input = this.embbeddable.getInput(); + return (input.events || []) as SerializedEvent[]; + } + + async count(): Promise { + return this.__list().length; + } + + async list(): Promise { + return this.__list(); + } +} diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 46cffab879684..62121cb0f23dd 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -30,6 +30,11 @@ export interface EmbeddableInput { hidePanelTitles?: boolean; isEmptyState?: boolean; + /** + * Reserved key for `ui_actions` events. + */ + events?: unknown; + /** * List of action IDs that this embeddable should not render. */ diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index f6ff9efca848b..4776204a8ab2f 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -630,6 +630,7 @@ describe('Execution', () => { }, }); expect(node2.debug?.rawError).toBeInstanceOf(Error); + expect(node2.debug?.rawError).toEqual(new Error('foo')); }); test('sets .debug object to expected shape', async () => { diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 7e7df822724ae..f70a32f2f09c1 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -21,7 +21,7 @@ import { keys, last, mapValues, reduce, zipObject } from 'lodash'; import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; -import { Defer } from '../../../kibana_utils/common'; +import { Defer, now } from '../../../kibana_utils/common'; import { RequestAdapter, DataAdapter } from '../../../inspector/common'; import { isExpressionValueError, ExpressionValueError } from '../expression_types/specs/error'; import { @@ -211,11 +211,11 @@ export class Execution< // actually have a `then` function which would be treated as a `Promise`. const { resolvedArgs } = await this.resolveArgs(fn, input, fnArgs); args = resolvedArgs; - timeStart = this.params.debug ? performance.now() : 0; + timeStart = this.params.debug ? now() : 0; const output = await this.invokeFunction(fn, input, resolvedArgs); if (this.params.debug) { - const timeEnd: number = performance.now(); + const timeEnd: number = now(); (link as ExpressionAstFunction).debug = { success: true, fn, @@ -229,9 +229,9 @@ export class Execution< if (getType(output) === 'error') return output; input = output; } catch (rawError) { - const timeEnd: number = this.params.debug ? performance.now() : 0; - rawError.message = `[${fnName}] > ${rawError.message}`; + const timeEnd: number = this.params.debug ? now() : 0; const error = createError(rawError) as ExpressionValueError; + error.error.message = `[${fnName}] > ${error.error.message}`; if (this.params.debug) { (link as ExpressionAstFunction).debug = { diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index 3b07674315dce..50120edc0c056 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -25,3 +25,4 @@ export * from './typed_json'; export { createGetterSetter, Get, Set } from './create_getter_setter'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; export { url } from './url'; +export { now } from './now'; diff --git a/src/plugins/kibana_utils/common/now.ts b/src/plugins/kibana_utils/common/now.ts new file mode 100644 index 0000000000000..e6c6d74552622 --- /dev/null +++ b/src/plugins/kibana_utils/common/now.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * Function that returns number in milliseconds since some undefined point in + * time. Use this function for performance measurements. + */ +export const now: () => number = + typeof performance === 'object' ? performance.now.bind(performance) : Date.now; diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index da70d9fe89525..275a9da96a2c4 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -39,7 +39,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/visualizations/public/embeddable/constants'; + +// TODO: can't import from '../../../../legacy/core_plugins/visualizations/public/' directly, +// because yarn build:types fails after trying to emit type declarations for whole visualizations plugin +// Bunch of errors like this: 'Return type of exported function has or is using private name 'SavedVis'' +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/visualizations/public/np_ready/public/embeddable/constants'; export interface OnSaveProps { newTitle: string; diff --git a/test/functional/apps/visualize/_linked_saved_searches.js b/test/functional/apps/visualize/_linked_saved_searches.ts similarity index 95% rename from test/functional/apps/visualize/_linked_saved_searches.js rename to test/functional/apps/visualize/_linked_saved_searches.ts index 37ec3f06f2ecd..345987a803394 100644 --- a/test/functional/apps/visualize/_linked_saved_searches.js +++ b/test/functional/apps/visualize/_linked_saved_searches.ts @@ -18,8 +18,9 @@ */ import expect from '@kbn/expect'; - -export default function({ getService, getPageObjects }) { +import { FtrProviderContext } from '../../ftr_provider_context'; +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: FtrProviderContext) { const filterBar = getService('filterBar'); const retry = getService('retry'); const PageObjects = getPageObjects([ @@ -40,8 +41,6 @@ export default function({ getService, getPageObjects }) { await filterBar.addFilter('extension.raw', 'is', 'jpg'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.saveSearch(savedSearchName); - // TODO: Remove this once https://github.com/elastic/kibana/issues/19750 is properly resolved - await PageObjects.common.sleep(500); }); it('should create a visualization from a saved search', async () => { diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 1bd6358749e11..382543822d8ac 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -44,6 +44,7 @@ import { Browsers } from './browsers'; const throttleOption: string = process.env.TEST_THROTTLE_NETWORK as string; const headlessBrowser: string = process.env.TEST_BROWSER_HEADLESS as string; const remoteDebug: string = process.env.TEST_REMOTE_DEBUG as string; +const certValidation: string = process.env.NODE_TLS_REJECT_UNAUTHORIZED as string; const SECOND = 1000; const MINUTE = 60 * SECOND; const NO_QUEUE_COMMANDS = ['getLog', 'getStatus', 'newSession', 'quit']; @@ -98,6 +99,9 @@ async function attemptToCreateCommand( // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md chromeOptions.push('headless', 'disable-gpu'); } + if (certValidation === '0') { + chromeOptions.push('ignore-certificate-errors'); + } if (remoteDebug === '1') { // Visit chrome://inspect in chrome to remotely view/debug chromeOptions.push('headless', 'disable-gpu', 'remote-debugging-port=9222'); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index b1cb9075434e8..bb084b3bb72a1 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -40,7 +40,7 @@ "xpack.taskManager": "legacy/plugins/task_manager", "xpack.transform": "legacy/plugins/transform", "xpack.triggersActionsUI": "plugins/triggers_actions_ui", - "xpack.upgradeAssistant": "legacy/plugins/upgrade_assistant", + "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": "legacy/plugins/uptime", "xpack.watcher": "plugins/watcher" }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx index 50ce918ea7037..3a6b4c5ebcaac 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx @@ -16,12 +16,7 @@ import { isNumber } from 'lodash'; import React from 'react'; import styled from 'styled-components'; import { ServiceNodeMetrics } from '../../../../../../../../plugins/apm/common/service_map'; -import { - asDuration, - asPercent, - toMicroseconds, - tpmUnit -} from '../../../../utils/formatters'; +import { asDuration, asPercent, tpmUnit } from '../../../../utils/formatters'; function LoadingSpinner() { return ( @@ -70,7 +65,7 @@ export function ServiceMetricList({ } ), description: isNumber(avgTransactionDuration) - ? asDuration(toMicroseconds(avgTransactionDuration, 'milliseconds')) + ? asDuration(avgTransactionDuration) : null }, { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index cc92d864282e7..d9e841092be56 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -6,7 +6,7 @@ // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/visualizations/public/embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/visualizations/public'; import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants'; export const EmbeddableTypes: { map: string; search: string; visualization: string } = { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index 6ac0d84bc9a73..0315a1f480911 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; -import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public/embeddable'; +import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public'; import { EmbeddableTypes, EmbeddableExpressionType, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression.test.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression.test.ts index 93d747537c34c..8694c0e2c7f9f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression.test.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('ui/new_platform'); import { embeddableInputToExpression } from './embeddable_input_to_expression'; import { SavedMapInput } from '../../functions/common/saved_map'; import { EmbeddableTypes } from '../../expression_types'; diff --git a/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js b/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js index 0ee4f76ebf9d0..67086883a9a32 100644 --- a/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js +++ b/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js @@ -268,6 +268,10 @@ export class JsonIndexFilePicker extends Component { maxFileSize: bytesToSize(MAX_FILE_SIZE), }} /> +
+ {i18n.translate('xpack.fileUpload.jsonIndexFilePicker.coordinateSystemAccepted', { + defaultMessage: 'Coordinates must be in EPSG:4326 coordinate reference system.', + })}{' '} ) } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap index 486a830d21b65..b5fe334f8415e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap @@ -17,25 +17,6 @@ exports[`TooltipHeader multiple features, multiple layers: locked should show pa pageCount={3} />
- - - - - @@ -139,25 +120,6 @@ exports[`TooltipHeader multiple features, single layer: locked should show pagin pageCount={2} /> - - - - - 1) { + if (!isLocked && filteredFeatures.length > 1) { headerItems.push( diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/index.ts b/x-pack/legacy/plugins/ml/common/constants/calendars.ts similarity index 86% rename from x-pack/legacy/plugins/upgrade_assistant/server/index.ts rename to x-pack/legacy/plugins/ml/common/constants/calendars.ts index 8b0704283509d..1a56257ca1304 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/index.ts +++ b/x-pack/legacy/plugins/ml/common/constants/calendars.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { plugin } from './np_ready'; +export const GLOBAL_CALENDAR = '_all'; diff --git a/x-pack/legacy/plugins/ml/common/types/file_datavisualizer.ts b/x-pack/legacy/plugins/ml/common/types/file_datavisualizer.ts new file mode 100644 index 0000000000000..bc03f82673a1f --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/file_datavisualizer.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface FindFileStructureResponse { + charset: string; + has_header_row: boolean; + has_byte_order_marker: boolean; + format: string; + field_stats: { + [fieldName: string]: { + count: number; + cardinality: number; + top_hits: Array<{ count: number; value: any }>; + }; + }; + sample_start: string; + num_messages_analyzed: number; + mappings: { + [fieldName: string]: { + type: string; + }; + }; + quote: string; + delimiter: string; + need_client_timezone: boolean; + num_lines_analyzed: number; + column_names: string[]; +} diff --git a/x-pack/legacy/plugins/ml/public/application/app.tsx b/x-pack/legacy/plugins/ml/public/application/app.tsx index 24cbfbfb346dd..add27193deb77 100644 --- a/x-pack/legacy/plugins/ml/public/application/app.tsx +++ b/x-pack/legacy/plugins/ml/public/application/app.tsx @@ -12,6 +12,7 @@ import 'ace'; import { AppMountParameters, CoreStart } from 'kibana/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { SecurityPluginSetup } from '../../../../../plugins/security/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { setDependencyCache, clearCache } from './util/dependency_cache'; @@ -20,6 +21,7 @@ import { MlRouter } from './routing'; export interface MlDependencies extends AppMountParameters { data: DataPublicPluginStart; + security: SecurityPluginSetup; __LEGACY: { XSRF: string; APP_URL: string; @@ -49,6 +51,7 @@ const App: FC = ({ coreStart, deps }) => { APP_URL: deps.__LEGACY.APP_URL, application: coreStart.application, http: coreStart.http, + security: deps.security, }); deps.onAppLeave(actions => { clearCache(); @@ -64,6 +67,7 @@ const App: FC = ({ coreStart, deps }) => { const services = { appName: 'ML', data: deps.data, + security: deps.security, ...coreStart, }; diff --git a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts index aaf539322809b..5fcd7c5473d3b 100644 --- a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -10,9 +10,11 @@ import { useKibana, KibanaReactContextValue, } from '../../../../../../../../src/plugins/kibana_react/public'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; interface StartPlugins { data: DataPublicPluginStart; + security: SecurityPluginSetup; } export type StartServices = CoreStart & StartPlugins; // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config.ts b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config.ts new file mode 100644 index 0000000000000..3344cdf991e6b --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config.ts @@ -0,0 +1,71 @@ +/* + * 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 { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; + +export function createFilebeatConfig( + index: string, + results: FindFileStructureResponse, + ingestPipelineId: string, + username: string | null +) { + return [ + 'filebeat.inputs:', + '- type: log', + ...getPaths(), + ...getEncoding(results), + ...getExcludeLines(results), + ...getMultiline(results), + '', + ...getProcessors(results), + 'output.elasticsearch:', + ' hosts: [""]', + ...getUserDetails(username), + ` index: "${index}"`, + ` pipeline: "${ingestPipelineId}"`, + '', + 'setup:', + ' template.enabled: false', + ' ilm.enabled: false', + ].join('\n'); +} + +function getPaths() { + const txt = i18n.translate('xpack.ml.fileDatavisualizer.fileBeatConfig.paths', { + defaultMessage: 'add path to your files here', + }); + return [' paths:', ` - '<${txt}>'`]; +} + +function getEncoding(results: any) { + return results.charset !== 'UTF-8' ? [` encoding: ${results.charset}`] : []; +} + +function getExcludeLines(results: any) { + return results.exclude_lines_pattern !== undefined + ? [` exclude_lines: ['${results.exclude_lines_pattern.replace(/'/g, "''")}']`] + : []; +} + +function getMultiline(results: any) { + return results.multiline_start_pattern !== undefined + ? [ + ' multiline:', + ` pattern: '${results.multiline_start_pattern.replace(/'/g, "''")}'`, + ' match: after', + ' negate: true', + ] + : []; +} + +function getProcessors(results: any) { + return results.need_client_timezone === true ? ['processors:', '- add_locale: ~', ''] : []; +} + +function getUserDetails(username: string | null) { + return username !== null ? [` username: "${username}"`, ' password: ""'] : []; +} diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx new file mode 100644 index 0000000000000..30fc74acbabf4 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/filebeat_config_flyout.tsx @@ -0,0 +1,162 @@ +/* + * 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. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useState, useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, + EuiCodeBlock, + EuiCode, + EuiCopy, +} from '@elastic/eui'; +import { createFilebeatConfig } from './filebeat_config'; +import { useMlKibana } from '../../../../contexts/kibana'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; + +export enum EDITOR_MODE { + HIDDEN, + READONLY, + EDITABLE, +} +interface Props { + index: string; + results: FindFileStructureResponse; + indexPatternId: string; + ingestPipelineId: string; + closeFlyout(): void; +} +export const FilebeatConfigFlyout: FC = ({ + index, + results, + indexPatternId, + ingestPipelineId, + closeFlyout, +}) => { + const [fileBeatConfig, setFileBeatConfig] = useState(''); + const [username, setUsername] = useState(null); + const { + services: { security }, + } = useMlKibana(); + + useEffect(() => { + security.authc.getCurrentUser().then(user => { + setUsername(user.username === undefined ? null : user.username); + }); + }, []); + + useEffect(() => { + const config = createFilebeatConfig(index, results, ingestPipelineId, username); + setFileBeatConfig(config); + }, [username]); + + return ( + + + + + + + + + + + + + + + + {copy => ( + + + + )} + + + + + + ); +}; + +const Contents: FC<{ + value: string; + index: string; + username: string | null; +}> = ({ value, index, username }) => { + return ( + + +
+ +
+
+ +

+ {index} }} + /> +

+

+ filebeat.yml }} + /> +

+ + + + {value} + + +

+ {username === null ? ( + {''}, + }} + /> + ) : ( + {username}, + password: {''}, + esUrl: {''}, + }} + /> + )} +

+
+ ); +}; diff --git a/x-pack/legacy/plugins/uptime/server/index.ts b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/index.ts similarity index 70% rename from x-pack/legacy/plugins/uptime/server/index.ts rename to x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/index.ts index d063f0d8c2288..9286b92c2ab97 100644 --- a/x-pack/legacy/plugins/uptime/server/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/filebeat_config_flyout/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { initServerWithKibana, KibanaServer } from './kibana.index'; -export { plugin } from './plugin'; +export { FilebeatConfigFlyout } from './filebeat_config_flyout'; diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index beb5918e277ae..bdfc27099a185 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -20,6 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import { importerFactory } from './importer'; import { ResultsLinks } from '../results_links'; +import { FilebeatConfigFlyout } from '../filebeat_config_flyout'; import { ImportProgress, IMPORT_STATUS } from '../import_progress'; import { ImportErrors } from '../import_errors'; import { ImportSummary } from '../import_summary'; @@ -64,6 +65,7 @@ const DEFAULT_STATE = { indexNameError: '', indexPatternNameError: '', timeFieldName: undefined, + isFilebeatFlyoutVisible: false, }; export class ImportView extends Component { @@ -384,6 +386,16 @@ export class ImportView extends Component { }); }; + showFilebeatFlyout = () => { + this.setState({ isFilebeatFlyoutVisible: true }); + this.props.hideBottomBar(); + }; + + closeFilebeatFlyout = () => { + this.setState({ isFilebeatFlyoutVisible: false }); + this.props.showBottomBar(); + }; + async loadIndexNames() { const indices = await ml.getIndices(); const indexNames = indices.map(i => i.name); @@ -424,6 +436,7 @@ export class ImportView extends Component { indexNameError, indexPatternNameError, timeFieldName, + isFilebeatFlyoutVisible, } = this.state; const createPipeline = pipelineString !== ''; @@ -549,7 +562,18 @@ export class ImportView extends Component { indexPatternId={indexPatternId} timeFieldName={timeFieldName} createIndexPattern={createIndexPattern} + showFilebeatFlyout={this.showFilebeatFlyout} /> + + {isFilebeatFlyoutVisible && ( + + )} )} diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js index 710fa49e64167..27899a58beed2 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js @@ -148,17 +148,17 @@ function populateFailures(error, failures, chunkCount) { } } -// The file structure endpoint sets the timezone to be {{ beat.timezone }} +// The file structure endpoint sets the timezone to be {{ event.timezone }} // as that's the variable Filebeat would send the client timezone in. // In this data import function the UI is effectively performing the role of Filebeat, // i.e. doing basic parsing, processing and conversion to JSON before forwarding to the ingest pipeline. // But it's not sending every single field that Filebeat would add, so the ingest pipeline -// cannot look for a beat.timezone variable in each input record. -// Therefore we need to replace {{ beat.timezone }} with the actual browser timezone +// cannot look for a event.timezone variable in each input record. +// Therefore we need to replace {{ event.timezone }} with the actual browser timezone function updatePipelineTimezone(ingestPipeline) { if (ingestPipeline !== undefined && ingestPipeline.processors && ingestPipeline.processors) { const dateProcessor = ingestPipeline.processors.find( - p => p.date !== undefined && p.date.timezone === '{{ beat.timezone }}' + p => p.date !== undefined && p.date.timezone === '{{ event.timezone }}' ); if (dateProcessor) { diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js index 840248817945a..c2d3ac69f0963 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js @@ -77,6 +77,11 @@ export class MessageImporter extends Importer { if (this.multilineStartRegex === null || line.match(this.multilineStartRegex) !== null) { this.addMessage(data, message); message = ''; + } else if (data.length === 0) { + // discard everything before the first line that is considered the first line of a message + // as it could be left over partial data from a spilt or rolled over log, + // or could be a blank line after the header in a csv file + return ''; } else { message += '\n'; } diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/index.js b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/index.js rename to x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/index.ts diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.js b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.js deleted file mode 100644 index aaebca2f58963..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.js +++ /dev/null @@ -1,182 +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 { FormattedMessage } from '@kbn/i18n/react'; -import React, { Component } from 'react'; - -import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui'; - -import moment from 'moment'; - -import { ml } from '../../../../services/ml_api_service'; -import { isFullLicense } from '../../../../license/check_license'; -import { checkPermission } from '../../../../privilege/check_privilege'; -import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes'; -import { withKibana } from '../../../../../../../../../../src/plugins/kibana_react/public'; - -const RECHECK_DELAY_MS = 3000; - -class ResultsLinksUI extends Component { - constructor(props) { - super(props); - - this.state = { - from: 'now-30m', - to: 'now', - }; - - this.recheckTimeout = null; - this.showCreateJobLink = true; - } - - componentDidMount() { - this.showCreateJobLink = checkPermission('canCreateJob') && mlNodesAvailable(); - // if this data has a time field, - // find the start and end times - if (this.props.timeFieldName !== undefined) { - this.updateTimeValues(); - } - } - - componentWillUnmount() { - clearTimeout(this.recheckTimeout); - } - - async updateTimeValues(recheck = true) { - const { index, timeFieldName } = this.props; - - const { from, to } = await getFullTimeRange(index, timeFieldName); - this.setState({ - from: from === null ? this.state.from : from, - to: to === null ? this.state.to : to, - }); - - // these links may have been drawn too quickly for the index to be ready - // to give us the correct start and end times. - // especially if the data was small. - // so if the start and end were null, try again in 3s - // the timeout is cleared when this component unmounts. just in case the user - // resets the form or navigates away within 3s - if (recheck && (from === null || to === null)) { - this.recheckTimeout = setTimeout(() => { - this.updateTimeValues(false); - }, RECHECK_DELAY_MS); - } - } - - render() { - const { index, indexPatternId, timeFieldName, createIndexPattern } = this.props; - - const { from, to } = this.state; - - const _g = - this.props.timeFieldName !== undefined - ? `&_g=(time:(from:'${from}',mode:quick,to:'${to}'))` - : ''; - - const { basePath } = this.props.kibana.services.http; - return ( - - {createIndexPattern && ( - - } - title={ - - } - description="" - href={`${basePath.get()}/app/kibana#/discover?&_a=(index:'${indexPatternId}')${_g}`} - /> - - )} - - {isFullLicense() === true && - timeFieldName !== undefined && - this.showCreateJobLink && - createIndexPattern && ( - - } - title={ - - } - description="" - href={`#/jobs/new_job/step/job_type?index=${indexPatternId}${_g}`} - /> - - )} - - {createIndexPattern && ( - - } - title={ - - } - description="" - href={`#/jobs/new_job/datavisualizer?index=${indexPatternId}${_g}`} - /> - - )} - - - } - title={ - - } - description="" - href={`${basePath.get()}/app/kibana#/management/elasticsearch/index_management/indices/filter/${index}`} - /> - - - - } - title={ - - } - description="" - href={`${basePath.get()}/app/kibana#/management/kibana/index_patterns/${ - createIndexPattern ? indexPatternId : '' - }`} - /> - - - ); - } -} - -export const ResultsLinks = withKibana(ResultsLinksUI); - -async function getFullTimeRange(index, timeFieldName) { - const query = { bool: { must: [{ query_string: { analyze_wildcard: true, query: '*' } }] } }; - const resp = await ml.getTimeFieldRange({ - index, - timeFieldName, - query, - }); - - return { - from: moment(resp.start.epoch).toISOString(), - to: moment(resp.end.epoch).toISOString(), - }; -} diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx new file mode 100644 index 0000000000000..debadba19051b --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useState, useEffect } from 'react'; +import moment from 'moment'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui'; +import { ml } from '../../../../services/ml_api_service'; +import { isFullLicense } from '../../../../license/check_license'; +import { checkPermission } from '../../../../privilege/check_privilege'; +import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes'; +import { useMlKibana } from '../../../../contexts/kibana'; + +const RECHECK_DELAY_MS = 3000; + +interface Props { + index: string; + indexPatternId: string; + timeFieldName?: string; + createIndexPattern: boolean; + showFilebeatFlyout(): void; +} + +export const ResultsLinks: FC = ({ + index, + indexPatternId, + timeFieldName, + createIndexPattern, + showFilebeatFlyout, +}) => { + const [duration, setDuration] = useState({ + from: 'now-30m', + to: 'now', + }); + const [showCreateJobLink, setShowCreateJobLink] = useState(false); + const [globalStateString, setGlobalStateString] = useState(''); + const { + services: { + http: { basePath }, + }, + } = useMlKibana(); + + useEffect(() => { + setShowCreateJobLink(checkPermission('canCreateJob') && mlNodesAvailable()); + updateTimeValues(); + }, []); + + useEffect(() => { + const _g = + timeFieldName !== undefined + ? `&_g=(time:(from:'${duration.from}',mode:quick,to:'${duration.to}'))` + : ''; + setGlobalStateString(_g); + }, [duration]); + + async function updateTimeValues(recheck = true) { + if (timeFieldName !== undefined) { + const { from, to } = await getFullTimeRange(index, timeFieldName); + setDuration({ + from: from === null ? duration.from : from, + to: to === null ? duration.to : to, + }); + + // these links may have been drawn too quickly for the index to be ready + // to give us the correct start and end times. + // especially if the data was small. + // so if the start and end were null, try again in 3s + if (recheck && (from === null || to === null)) { + setTimeout(() => { + updateTimeValues(false); + }, RECHECK_DELAY_MS); + } + } + } + + return ( + + {createIndexPattern && ( + + } + title={ + + } + description="" + href={`${basePath.get()}/app/kibana#/discover?&_a=(index:'${indexPatternId}')${globalStateString}`} + /> + + )} + + {isFullLicense() === true && + timeFieldName !== undefined && + showCreateJobLink && + createIndexPattern && ( + + } + title={ + + } + description="" + href={`#/jobs/new_job/step/job_type?index=${indexPatternId}${globalStateString}`} + /> + + )} + + {createIndexPattern && ( + + } + title={ + + } + description="" + href={`#/jobs/new_job/datavisualizer?index=${indexPatternId}${globalStateString}`} + /> + + )} + + + } + title={ + + } + description="" + href={`${basePath.get()}/app/kibana#/management/elasticsearch/index_management/indices/filter/${index}`} + /> + + + + } + title={ + + } + description="" + href={`${basePath.get()}/app/kibana#/management/kibana/index_patterns/${ + createIndexPattern ? indexPatternId : '' + }`} + /> + + + } + title={ + + } + description="" + onClick={showFilebeatFlyout} + /> + + + ); +}; + +async function getFullTimeRange(index: string, timeFieldName: string) { + const query = { bool: { must: [{ query_string: { analyze_wildcard: true, query: '*' } }] } }; + const resp = await ml.getTimeFieldRange({ + index, + timeFieldName, + query, + }); + + return { + from: moment(resp.start.epoch).toISOString(), + to: moment(resp.end.epoch).toISOString(), + }; +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx index 919972186761a..1e7327552623e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/calendars_selection.tsx @@ -23,6 +23,7 @@ import { JobCreatorContext } from '../../../../../job_creator_context'; import { Description } from './description'; import { ml } from '../../../../../../../../../services/ml_api_service'; import { Calendar } from '../../../../../../../../../../../common/types/calendars'; +import { GLOBAL_CALENDAR } from '../../../../../../../../../../../common/constants/calendars'; export const CalendarsSelection: FC = () => { const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext); @@ -35,7 +36,9 @@ export const CalendarsSelection: FC = () => { async function loadCalendars() { setIsLoading(true); - const calendars = await ml.calendars(); + const calendars = (await ml.calendars()).filter( + c => c.job_ids.includes(GLOBAL_CALENDAR) === false + ); setOptions(calendars.map(c => ({ label: c.calendar_id, value: c }))); setSelectedOptions(selectedCalendars.map(c => ({ label: c.calendar_id, value: c }))); setIsLoading(false); diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap index 2f5eb596a157b..21f505cff9aec 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/__snapshots__/new_calendar.test.js.snap @@ -22,6 +22,7 @@ exports[`NewCalendar Renders new calendar form 1`] = ` eventsList={Array []} groupIds={Array []} isEdit={false} + isGlobalCalendar={false} isNewCalendarIdValid={true} jobIds={Array []} onCalendarIdChange={[Function]} @@ -30,6 +31,7 @@ exports[`NewCalendar Renders new calendar form 1`] = ` onDescriptionChange={[Function]} onEdit={[Function]} onEventDelete={[Function]} + onGlobalCalendarChange={[Function]} onGroupSelection={[Function]} onJobSelection={[Function]} saving={false} diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap index 0e7db62e44b51..acce01f1994db 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap @@ -84,59 +84,19 @@ exports[`CalendarForm Renders calendar form 1`] = ` value="" /> - - } - labelType="label" - > - - - + } - labelType="label" - > - - + name="switch" + /> diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.js index fffcdf4c516f8..62daced72ceb2 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.js @@ -18,6 +18,7 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiSwitch, } from '@elastic/eui'; import { EventsTable } from '../events_table'; @@ -68,6 +69,8 @@ export const CalendarForm = ({ selectedGroupOptions, selectedJobOptions, showNewEventModal, + isGlobalCalendar, + onGlobalCalendarChange, }) => { const msg = i18n.translate('xpack.ml.calendarsEdit.calendarForm.allowedCharactersDescription', { defaultMessage: @@ -81,7 +84,9 @@ export const CalendarForm = ({ return ( - {!isEdit && ( + {isEdit === true ? ( + + ) : (

@@ -128,39 +133,59 @@ export const CalendarForm = ({ )} - {isEdit && } - - } - > - - - + + } - > - - + checked={isGlobalCalendar} + onChange={onGlobalCalendarChange} + /> + + {isGlobalCalendar === false && ( + <> + + + + } + > + + + + + } + > + + + + )} @@ -240,4 +265,6 @@ CalendarForm.propTypes = { selectedGroupOptions: PropTypes.array.isRequired, selectedJobOptions: PropTypes.array.isRequired, showNewEventModal: PropTypes.func.isRequired, + isGlobalCalendar: PropTypes.bool.isRequired, + onGlobalCalendarChange: PropTypes.func.isRequired, }; diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/new_calendar.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/new_calendar.js index 935e67ec05eff..815d1565d5bc4 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/new_calendar.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/new_calendar.js @@ -19,6 +19,7 @@ import { NewEventModal } from './new_event_modal'; import { ImportModal } from './import_modal'; import { ml } from '../../../services/ml_api_service'; import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { GLOBAL_CALENDAR } from '../../../../../common/constants/calendars'; class NewCalendarUI extends Component { static propTypes = { @@ -46,6 +47,7 @@ class NewCalendarUI extends Component { events: [], saving: false, selectedCalendar: undefined, + isGlobalCalendar: false, }; } @@ -65,6 +67,7 @@ class NewCalendarUI extends Component { let eventsList = []; let selectedCalendar; let formCalendarId = ''; + let isGlobalCalendar = false; // Editing existing calendar. if (this.props.calendarId !== undefined) { @@ -74,13 +77,17 @@ class NewCalendarUI extends Component { formCalendarId = selectedCalendar.calendar_id; eventsList = selectedCalendar.events; - selectedCalendar.job_ids.forEach(id => { - if (jobIds.find(jobId => jobId === id)) { - selectedJobOptions.push({ label: id }); - } else if (groupIds.find(groupId => groupId === id)) { - selectedGroupOptions.push({ label: id }); - } - }); + if (selectedCalendar.job_ids.includes(GLOBAL_CALENDAR)) { + isGlobalCalendar = true; + } else { + selectedCalendar.job_ids.forEach(id => { + if (jobIds.find(jobId => jobId === id)) { + selectedJobOptions.push({ label: id }); + } else if (groupIds.find(groupId => groupId === id)) { + selectedGroupOptions.push({ label: id }); + } + }); + } } } @@ -96,6 +103,7 @@ class NewCalendarUI extends Component { selectedJobOptions, selectedGroupOptions, selectedCalendar, + isGlobalCalendar, }); } catch (error) { console.log(error); @@ -181,10 +189,15 @@ class NewCalendarUI extends Component { events, selectedGroupOptions, selectedJobOptions, + isGlobalCalendar, } = this.state; - const jobIds = selectedJobOptions.map(option => option.label); - const groupIds = selectedGroupOptions.map(option => option.label); + const allIds = isGlobalCalendar + ? [GLOBAL_CALENDAR] + : [ + ...selectedJobOptions.map(option => option.label), + ...selectedGroupOptions.map(option => option.label), + ]; // Reduce events to fields expected by api const eventsToSave = events.map(event => ({ @@ -198,7 +211,7 @@ class NewCalendarUI extends Component { calendarId: formCalendarId, description, events: eventsToSave, - job_ids: [...jobIds, ...groupIds], + job_ids: allIds, }; return calendar; @@ -214,6 +227,12 @@ class NewCalendarUI extends Component { })); }; + onGlobalCalendarChange = ({ currentTarget }) => { + this.setState({ + isGlobalCalendar: currentTarget.checked, + }); + }; + onJobSelection = selectedJobOptions => { this.setState({ selectedJobOptions, @@ -295,6 +314,7 @@ class NewCalendarUI extends Component { selectedCalendar, selectedJobOptions, selectedGroupOptions, + isGlobalCalendar, } = this.state; let modal = ''; @@ -351,6 +371,8 @@ class NewCalendarUI extends Component { selectedJobOptions={selectedJobOptions} onCreateGroupOption={this.onCreateGroupOption} showNewEventModal={this.showNewEventModal} + isGlobalCalendar={isGlobalCalendar} + onGlobalCalendarChange={this.onGlobalCalendarChange} /> {modal} diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/utils.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/utils.js index e4ab6677accf5..efc54c181fdc1 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/utils.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/edit/utils.js @@ -73,14 +73,17 @@ function getCalendars() { export function getCalendarSettingsData() { return new Promise(async (resolve, reject) => { try { - const data = await Promise.all([getJobIds(), getGroupIds(), getCalendars()]); + const [jobIds, groupIds, calendars] = await Promise.all([ + getJobIds(), + getGroupIds(), + getCalendars(), + ]); - const formattedData = { - jobIds: data[0], - groupIds: data[1], - calendars: data[2], - }; - resolve(formattedData); + resolve({ + jobIds, + groupIds, + calendars, + }); } catch (error) { console.log(error); reject(error); diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap index ff74c592b2b0f..14b65a04ce599 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/__snapshots__/table.test.js.snap @@ -16,6 +16,7 @@ exports[`CalendarsListTable renders the table with all calendars 1`] = ` Object { "field": "job_ids_string", "name": "Jobs", + "render": [Function], "sortable": true, "truncateText": true, }, diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js index bd1dafcd6c0aa..be41eabd5ae2d 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/table/table.js @@ -12,6 +12,8 @@ import { EuiButton, EuiLink, EuiInMemoryTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { GLOBAL_CALENDAR } from '../../../../../../common/constants/calendars'; + export const CalendarsListTable = ({ calendarsList, onDeleteClick, @@ -52,6 +54,18 @@ export const CalendarsListTable = ({ }), sortable: true, truncateText: true, + render: jobList => { + return jobList === GLOBAL_CALENDAR ? ( + + + + ) : ( + jobList + ); + }, }, { field: 'events_length', diff --git a/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts index 8857485a58644..f837d90dba8fe 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts @@ -20,6 +20,7 @@ import { ChromeRecentlyAccessed, IBasePath, } from 'kibana/public'; +import { SecurityPluginSetup } from '../../../../../../plugins/security/public'; export interface DependencyCache { timefilter: TimefilterSetup | null; @@ -38,6 +39,7 @@ export interface DependencyCache { APP_URL: string | null; application: ApplicationStart | null; http: HttpStart | null; + security: SecurityPluginSetup | null; } const cache: DependencyCache = { @@ -57,6 +59,7 @@ const cache: DependencyCache = { APP_URL: null, application: null, http: null, + security: null, }; export function setDependencyCache(deps: Partial) { @@ -189,6 +192,13 @@ export function getHttp() { return cache.http; } +export function getSecurity() { + if (cache.security === null) { + throw new Error("security hasn't been initialized"); + } + return cache.security; +} + export function clearCache() { console.log('clearing dependency cache'); // eslint-disable-line no-console Object.keys(cache).forEach(k => { diff --git a/x-pack/legacy/plugins/ml/public/legacy.ts b/x-pack/legacy/plugins/ml/public/legacy.ts index bf431f0986d68..40a1afa06b5a6 100644 --- a/x-pack/legacy/plugins/ml/public/legacy.ts +++ b/x-pack/legacy/plugins/ml/public/legacy.ts @@ -6,14 +6,16 @@ import chrome from 'ui/chrome'; import { npSetup, npStart } from 'ui/new_platform'; - import { PluginInitializerContext } from 'src/core/public'; +import { SecurityPluginSetup } from '../../../../plugins/security/public'; + import { plugin } from '.'; const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, { data: npStart.plugins.data, + security: ((npSetup.plugins as unknown) as { security: SecurityPluginSetup }).security, // security isn't in the PluginsSetup interface, but does exist __LEGACY: { XSRF: chrome.getXsrfToken(), // @ts-ignore getAppUrl is missing from chrome's definition diff --git a/x-pack/legacy/plugins/ml/public/plugin.ts b/x-pack/legacy/plugins/ml/public/plugin.ts index 79af300bce4ec..cb39b31a32b14 100644 --- a/x-pack/legacy/plugins/ml/public/plugin.ts +++ b/x-pack/legacy/plugins/ml/public/plugin.ts @@ -8,7 +8,7 @@ import { Plugin, CoreStart, CoreSetup } from 'src/core/public'; import { MlDependencies } from './application/app'; export class MlPlugin implements Plugin { - setup(core: CoreSetup, { data, __LEGACY }: MlDependencies) { + setup(core: CoreSetup, { data, security, __LEGACY }: MlDependencies) { core.application.register({ id: 'ml', title: 'Machine learning', @@ -21,6 +21,7 @@ export class MlPlugin implements Plugin { onAppLeave: params.onAppLeave, data, __LEGACY, + security, }); }, }); diff --git a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js b/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js index 9317d3c6c3e07..e3092abb5d34e 100644 --- a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js +++ b/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js @@ -450,6 +450,18 @@ export const elasticsearchJsPlugin = (Client, config, components) => { method: 'POST', }); + ml.records = ca({ + url: { + fmt: '/_ml/anomaly_detectors/<%=jobId%>/results/records', + req: { + jobId: { + type: 'string', + }, + }, + }, + method: 'POST', + }); + ml.buckets = ca({ urls: [ { diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts b/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts index 19f2eda466179..488839f68b3fe 100644 --- a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts +++ b/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts @@ -6,6 +6,8 @@ import Boom from 'boom'; +import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; + export interface CalendarEvent { calendar_id?: string; event_id?: string; @@ -32,7 +34,7 @@ export class EventManager { // jobId is optional async getAllEvents(jobId?: string) { - const calendarId = '_all'; + const calendarId = GLOBAL_CALENDAR; try { const resp = await this._client('ml.events', { calendarId, diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index fd5b5221393fc..9f30f609c60b6 100644 --- a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -6,6 +6,7 @@ import Boom from 'boom'; import { RequestHandlerContext } from 'kibana/server'; +import { FindFileStructureResponse } from '../../../common/types/file_datavisualizer'; export type InputData = any[]; @@ -20,31 +21,7 @@ export type FormattedOverrides = InputOverrides & { }; export interface AnalysisResult { - results: { - charset: string; - has_header_row: boolean; - has_byte_order_marker: boolean; - format: string; - field_stats: { - [fieldName: string]: { - count: number; - cardinality: number; - top_hits: Array<{ count: number; value: any }>; - }; - }; - sample_start: string; - num_messages_analyzed: number; - mappings: { - [fieldName: string]: { - type: string; - }; - }; - quote: string; - delimiter: string; - need_client_timezone: boolean; - num_lines_analyzed: number; - column_names: string[]; - }; + results: FindFileStructureResponse; overrides?: FormattedOverrides; } diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js b/x-pack/legacy/plugins/ml/server/models/job_service/groups.js index 91f82f04a9a0c..6fbc071ef9854 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js +++ b/x-pack/legacy/plugins/ml/server/models/job_service/groups.js @@ -5,6 +5,7 @@ */ import { CalendarManager } from '../calendar'; +import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; export function groupsProvider(callWithRequest) { const calMngr = new CalendarManager(callWithRequest); @@ -12,11 +13,13 @@ export function groupsProvider(callWithRequest) { async function getAllGroups() { const groups = {}; const jobIds = {}; - const [JOBS, CALENDARS] = [0, 1]; - const results = await Promise.all([callWithRequest('ml.jobs'), calMngr.getAllCalendars()]); + const [{ jobs }, calendars] = await Promise.all([ + callWithRequest('ml.jobs'), + calMngr.getAllCalendars(), + ]); - if (results[JOBS] && results[JOBS].jobs) { - results[JOBS].jobs.forEach(job => { + if (jobs) { + jobs.forEach(job => { jobIds[job.job_id] = null; if (job.groups !== undefined) { job.groups.forEach(g => { @@ -33,10 +36,11 @@ export function groupsProvider(callWithRequest) { } }); } - if (results[CALENDARS]) { - results[CALENDARS].forEach(cal => { + if (calendars) { + calendars.forEach(cal => { cal.job_ids.forEach(jId => { - if (jobIds[jId] === undefined) { + // don't include _all in the calendar groups list + if (jId !== GLOBAL_CALENDAR && jobIds[jId] === undefined) { if (groups[jId] === undefined) { groups[jId] = { id: jId, diff --git a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts index 927646e4f0acc..99dbdec9e945b 100644 --- a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts @@ -376,11 +376,57 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) { }) ); + /** + * @apiGroup AnomalyDetectors + * + * @api {post} /api/ml/anomaly_detectors/:jobId/results/records Retrieves anomaly records for a job. + * @apiName GetRecords + * @apiDescription Retrieves anomaly records for a job. + * + * @apiParam {String} jobId Job ID. + * + * @apiSuccess {Number} count + * @apiSuccess {Object[]} records + */ + router.post( + { + path: '/api/ml/anomaly_detectors/{jobId}/results/records', + validate: { + params: schema.object({ + jobId: schema.string(), + }), + body: schema.object({ + desc: schema.maybe(schema.boolean()), + end: schema.maybe(schema.string()), + exclude_interim: schema.maybe(schema.boolean()), + 'page.from': schema.maybe(schema.number()), + 'page.size': schema.maybe(schema.number()), + record_score: schema.maybe(schema.number()), + sort: schema.maybe(schema.string()), + start: schema.maybe(schema.string()), + }), + }, + }, + licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + try { + const results = await context.ml!.mlClient.callAsCurrentUser('ml.records', { + jobId: request.params.jobId, + ...request.body, + }); + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + /** * @apiGroup AnomalyDetectors * * @api {post} /api/ml/anomaly_detectors/:jobId/results/buckets Obtain bucket scores for the specified job ID - * @apiName GetOverallBuckets + * @apiName GetBuckets * @apiDescription The get buckets API presents a chronological view of the records, grouped by bucket. * * @apiParam {String} jobId Job ID. diff --git a/x-pack/legacy/plugins/ml/server/routes/apidoc.json b/x-pack/legacy/plugins/ml/server/routes/apidoc.json index 946e3bd71d6c3..c5aa3e4d792fd 100644 --- a/x-pack/legacy/plugins/ml/server/routes/apidoc.json +++ b/x-pack/legacy/plugins/ml/server/routes/apidoc.json @@ -31,6 +31,8 @@ "DeleteAnomalyDetectorsJob", "ValidateAnomalyDetector", "ForecastAnomalyDetector", + "GetRecords", + "GetBuckets", "GetOverallBuckets", "GetCategories", "FileDataVisualizer", diff --git a/x-pack/legacy/plugins/siem/cypress/integration/events_viewer.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/events_viewer.spec.ts index e44c8f4459ba9..446db89ec09dc 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/events_viewer.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/events_viewer.spec.ts @@ -8,7 +8,6 @@ import { FIELDS_BROWSER_CHECKBOX, FIELDS_BROWSER_CONTAINER, FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, - FIELDS_BROWSER_TITLE, } from '../screens/fields_browser'; import { HEADER_SUBTITLE, @@ -61,12 +60,6 @@ describe('Events Viewer', () => { cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); }); - it('renders the fields browser with the expected title when the Events Viewer Fields Browser button is clicked', () => { - cy.get(FIELDS_BROWSER_TITLE) - .invoke('text') - .should('eq', 'Customize Columns'); - }); - it('displays the `default ECS` category (by default)', () => { cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) .invoke('text') diff --git a/x-pack/legacy/plugins/siem/cypress/integration/fields_browser.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/fields_browser.spec.ts index 095fc30356fd4..8dddd97f2d830 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/fields_browser.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/fields_browser.spec.ts @@ -15,7 +15,6 @@ import { FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, FIELDS_BROWSER_SELECTED_CATEGORY_COUNT, FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT, - FIELDS_BROWSER_TITLE, } from '../screens/fields_browser'; import { @@ -58,12 +57,6 @@ describe('Fields Browser', () => { clearFieldsBrowser(); }); - it('renders the fields browser with the expected title when the Fields button is clicked', () => { - cy.get(FIELDS_BROWSER_TITLE) - .invoke('text') - .should('eq', 'Customize Columns'); - }); - it('displays the `default ECS` category (by default)', () => { cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) .invoke('text') diff --git a/x-pack/legacy/plugins/siem/cypress/support/index.js b/x-pack/legacy/plugins/siem/cypress/support/index.js index 9b86c2ffa94c6..37fa920a8bc31 100644 --- a/x-pack/legacy/plugins/siem/cypress/support/index.js +++ b/x-pack/legacy/plugins/siem/cypress/support/index.js @@ -26,5 +26,11 @@ Cypress.Cookies.defaults({ whitelist: 'sid', }); +Cypress.on('uncaught:exception', err => { + if (err.message.includes('ResizeObserver loop limit exceeded')) { + return false; + } +}); + // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/x-pack/legacy/plugins/siem/public/components/charts/__snapshots__/barchart.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/charts/__snapshots__/barchart.test.tsx.snap index 12b9afb661da1..c330676e9219e 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/__snapshots__/barchart.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/charts/__snapshots__/barchart.test.tsx.snap @@ -1,5 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BarChartBaseComponent render with customized configs should 2 render BarSeries 1`] = `[Function]`; - exports[`BarChartBaseComponent render with default configs if no customized configs given should 2 render BarSeries 1`] = `[Function]`; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 27f0222b96b77..3c2de28ae423c 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -331,7 +331,7 @@ describe('AreaChart', () => { }); it(`should render area chart`, () => { - expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(1); + expect(shallowWrapper.find('AreaChartBase')).toHaveLength(1); expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(0); }); }); @@ -344,7 +344,7 @@ describe('AreaChart', () => { }); it(`should render a chart place holder`, () => { - expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(0); + expect(shallowWrapper.find('AreaChartBase')).toHaveLength(0); expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(1); }); } diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index b66cc77e30aad..f3b2b736ed87d 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -146,8 +146,4 @@ export const AreaChartComponent: React.FC = ({ areaChar ); }; -AreaChartComponent.displayName = 'AreaChartComponent'; - export const AreaChart = React.memo(AreaChartComponent); - -AreaChart.displayName = 'AreaChart'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index 0b6635b04d380..272c41833f368 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; -import { BarSeries, ScaleType, Axis } from '@elastic/charts'; +import { Chart, BarSeries, Axis, ScaleType } from '@elastic/charts'; jest.mock('../../lib/kibana'); @@ -139,7 +139,7 @@ describe('BarChartBaseComponent', () => { }); it('should render two bar series', () => { - expect(shallowWrapper.find('Chart')).toHaveLength(1); + expect(shallowWrapper.find(Chart)).toHaveLength(1); }); }); @@ -167,7 +167,6 @@ describe('BarChartBaseComponent', () => { }); it(`should ${mockBarChartData.length} render BarSeries`, () => { - expect(shallow).toMatchSnapshot(); expect(shallowWrapper.find(BarSeries)).toHaveLength(mockBarChartData.length); }); @@ -265,7 +264,7 @@ describe('BarChartBaseComponent', () => { }); it('should not render without height and width', () => { - expect(shallowWrapper.find('Chart')).toHaveLength(0); + expect(shallowWrapper.find(Chart)).toHaveLength(0); }); }); }); @@ -278,7 +277,7 @@ describe.each(chartDataSets)('BarChart with valid data [%o]', data => { }); it(`should render chart`, () => { - expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(1); + expect(shallowWrapper.find('BarChartBase')).toHaveLength(1); expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(0); }); }); @@ -291,7 +290,7 @@ describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', data => { }); it(`should render a ChartPlaceHolder`, () => { - expect(shallowWrapper.find('WrappedByAutoSizer')).toHaveLength(0); + expect(shallowWrapper.find('BarChartBase')).toHaveLength(0); expect(shallowWrapper.find('ChartPlaceHolder')).toHaveLength(1); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx index 03b412f575646..7377bcbe7050f 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx @@ -16,7 +16,9 @@ import { TickFormatter, Position, } from '@elastic/charts'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; + import { useUiSetting } from '../../lib/kibana'; import { DEFAULT_DARK_MODE } from '../../../common/constants'; @@ -54,7 +56,7 @@ export interface ChartSeriesData { color?: string | undefined; } -export const WrappedByAutoSizer = styled.div<{ height?: string }>` +const WrappedByAutoSizerComponent = styled.div<{ height?: string }>` ${style => ` height: ${style.height != null ? style.height : defaultChartHeight}; @@ -66,7 +68,9 @@ export const WrappedByAutoSizer = styled.div<{ height?: string }>` } `; -WrappedByAutoSizer.displayName = 'WrappedByAutoSizer'; +WrappedByAutoSizerComponent.displayName = 'WrappedByAutoSizer'; + +export const WrappedByAutoSizer = React.memo(WrappedByAutoSizerComponent); export enum SeriesType { BAR = 'bar', @@ -96,8 +100,9 @@ const theme: PartialTheme = { export const useTheme = () => { const isDarkMode = useUiSetting(DEFAULT_DARK_MODE); const defaultTheme = isDarkMode ? DARK_THEME : LIGHT_THEME; + const themeValue = useMemo(() => mergeWithDefaultTheme(theme, defaultTheme), []); - return mergeWithDefaultTheme(theme, defaultTheme); + return themeValue; }; export const chartDefaultSettings = { 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 cf958bfd75d3b..7d84403b87f8d 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,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import React, { createContext, useContext, useEffect } from 'react'; import { Draggable, @@ -14,6 +13,7 @@ import { } from 'react-beautiful-dnd'; import { connect, ConnectedProps } from 'react-redux'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { EuiPortal } from '@elastic/eui'; import { dragAndDropActions } from '../../store/drag_and_drop'; @@ -122,7 +122,7 @@ const DraggableWrapperComponent = React.memo( }, (prevProps, nextProps) => { return ( - isEqual(prevProps.dataProvider, nextProps.dataProvider) && + deepEqual(prevProps.dataProvider, nextProps.dataProvider) && prevProps.render !== nextProps.render && prevProps.truncate === nextProps.truncate ); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index 2a4d08ea214bc..a913186d9ad3b 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -5,10 +5,10 @@ */ import { EuiPanel } from '@elastic/eui'; -import deepEqual from 'fast-deep-equal'; -import { getOr, isEmpty, isEqual, union } from 'lodash/fp'; +import { getOr, isEmpty, union } from 'lodash/fp'; import React, { useMemo } from 'react'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import useResizeObserver from 'use-resize-observer/polyfilled'; import { BrowserFields } from '../../containers/source'; @@ -228,7 +228,7 @@ const EventsViewerComponent: React.FC = ({ export const EventsViewer = React.memo( EventsViewerComponent, (prevProps, nextProps) => - isEqual(prevProps.browserFields, nextProps.browserFields) && + deepEqual(prevProps.browserFields, nextProps.browserFields) && prevProps.columns === nextProps.columns && prevProps.dataProviders === nextProps.dataProviders && prevProps.deletedEventIds === nextProps.deletedEventIds && @@ -241,9 +241,9 @@ export const EventsViewer = React.memo( prevProps.itemsPerPage === nextProps.itemsPerPage && prevProps.itemsPerPageOptions === nextProps.itemsPerPageOptions && prevProps.kqlMode === nextProps.kqlMode && - isEqual(prevProps.query, nextProps.query) && + deepEqual(prevProps.query, nextProps.query) && prevProps.start === nextProps.start && prevProps.sort === nextProps.sort && - isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && + deepEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && prevProps.utilityBar === nextProps.utilityBar ); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx index 762ae8497dadb..9b31be40dd955 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import React, { useCallback, useMemo, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store'; import { inputsActions, timelineActions } from '../../store/actions'; @@ -197,23 +197,23 @@ export const StatefulEventsViewer = connector( StatefulEventsViewerComponent, (prevProps, nextProps) => prevProps.id === nextProps.id && - isEqual(prevProps.columns, nextProps.columns) && - isEqual(prevProps.dataProviders, nextProps.dataProviders) && + deepEqual(prevProps.columns, nextProps.columns) && + deepEqual(prevProps.dataProviders, nextProps.dataProviders) && prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && - isEqual(prevProps.filters, nextProps.filters) && + deepEqual(prevProps.filters, nextProps.filters) && prevProps.isLive === nextProps.isLive && prevProps.itemsPerPage === nextProps.itemsPerPage && - isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && + deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && prevProps.kqlMode === nextProps.kqlMode && - isEqual(prevProps.query, nextProps.query) && - isEqual(prevProps.sort, nextProps.sort) && + deepEqual(prevProps.query, nextProps.query) && + deepEqual(prevProps.sort, nextProps.sort) && prevProps.start === nextProps.start && - isEqual(prevProps.pageFilters, nextProps.pageFilters) && + deepEqual(prevProps.pageFilters, nextProps.pageFilters) && prevProps.showCheckboxes === nextProps.showCheckboxes && prevProps.showRowRenderers === nextProps.showRowRenderers && prevProps.start === nextProps.start && - isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && + deepEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && prevProps.utilityBar === nextProps.utilityBar ) ); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index eb41773bb21c8..22fc9f27ce26c 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -6,7 +6,7 @@ import { EuiBadge } from '@elastic/eui'; import { defaultTo, getOr } from 'lodash/fp'; -import React from 'react'; +import React, { useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import styled from 'styled-components'; @@ -58,28 +58,39 @@ export const FlyoutComponent = React.memo( timelineId, usersViewing, width, - }) => ( - <> - - showTimeline({ id: timelineId, show: false })} + }) => { + const handleClose = useCallback(() => showTimeline({ id: timelineId, show: false }), [ + showTimeline, + timelineId, + ]); + const handleOpen = useCallback(() => showTimeline({ id: timelineId, show: true }), [ + showTimeline, + timelineId, + ]); + + return ( + <> + + + {children} + + + - {children} - - - showTimeline({ id: timelineId, show: true })} - /> - - ) + onOpen={handleOpen} + /> + + ); + } ); FlyoutComponent.displayName = 'FlyoutComponent'; diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx index 405a8f060948e..d6f8143745356 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; -import { getOr } from 'lodash/fp'; +import { getOr, omit } from 'lodash/fp'; import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; @@ -162,7 +162,11 @@ const makeMapStateToProps = () => { const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); const getTimelineQuery = inputsSelectors.timelineQueryByIdSelector(); const mapStateToProps = (state: State, { inputId = 'global', queryId }: OwnProps) => { - return inputId === 'global' ? getGlobalQuery(state, queryId) : getTimelineQuery(state, queryId); + const props = + inputId === 'global' ? getGlobalQuery(state, queryId) : getTimelineQuery(state, queryId); + // refetch caused unnecessary component rerender and it was even not used + const propsWithoutRefetch = omit('refetch', props); + return propsWithoutRefetch; }; return mapStateToProps; }; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx index e40cc887ab5ff..8a754cb47475f 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import isEqual from 'lodash/fp/isEqual'; -import deepEqual from 'fast-deep-equal'; import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; +import deepEqual from 'fast-deep-equal'; import { useKibana } from '../../lib/kibana'; import { RouteSpyState } from '../../utils/route/types'; @@ -81,8 +80,8 @@ export const SiemNavigationRedux = compose< (prevProps, nextProps) => prevProps.pathName === nextProps.pathName && prevProps.search === nextProps.search && - isEqual(prevProps.navTabs, nextProps.navTabs) && - isEqual(prevProps.urlState, nextProps.urlState) && + deepEqual(prevProps.navTabs, nextProps.navTabs) && + deepEqual(prevProps.urlState, nextProps.urlState) && deepEqual(prevProps.state, nextProps.state) ) ); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx index 853ba7ae23414..678faff7654db 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx @@ -7,7 +7,7 @@ /* eslint-disable react/display-name */ import { has } from 'lodash/fp'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { hostsActions } from '../../../../store/hosts'; @@ -100,10 +100,12 @@ const AuthenticationTableComponent = React.memo( [type, updateTableActivePage] ); + const columns = useMemo(() => getAuthenticationColumnsCurated(type), [type]); + return ( ( [type, updateTableActivePage] ); + const columns = useMemo(() => getUncommonColumnsCurated(type), [type]); + return ( ]; -export const getNetworkDnsColumns = (type: networkModel.NetworkType): NetworkDnsColumns => [ +export const getNetworkDnsColumns = (): NetworkDnsColumns => [ { field: `node.${NetworkDnsFields.dnsName}`, name: i18n.REGISTERED_DOMAIN, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx index f3fe98936a55d..c1dd96c5c96f9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { networkActions } from '../../../../store/actions'; import { @@ -93,7 +93,7 @@ export const NetworkDnsTableComponent = React.memo( field: criteria.sort.field.split('.')[1] as NetworkDnsFields, direction: criteria.sort.direction as Direction, }; - if (!isEqual(newDnsSortField, sort)) { + if (!deepEqual(newDnsSortField, sort)) { updateNetworkTable({ networkType: type, tableType, @@ -115,10 +115,12 @@ export const NetworkDnsTableComponent = React.memo( [type, updateNetworkTable, isPtrIncluded] ); + const columns = useMemo(() => getNetworkDnsColumns(), []); + return ( = ({ const sorting = { field: `node.${NetworkHttpFields.requestCount}`, direction: sort.direction }; + const columns = useMemo(() => getNetworkHttpColumns(tableType), [tableType]); + return ( = ({ field: field as NetworkTopTablesFields, direction: newSortDirection as Direction, }; - if (!isEqual(newTopNFlowSort, sort)) { + if (!deepEqual(newTopNFlowSort, sort)) { updateNetworkTable({ networkType: type, tableType, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx index 77abae68b76bf..d1512699cc709 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { networkActions } from '../../../../store/network'; import { TlsEdges, TlsSortField, TlsFields, Direction } from '../../../../graphql/types'; @@ -91,7 +91,7 @@ const TlsTableComponent = React.memo( field: getSortFromString(splitField[splitField.length - 1]), direction: criteria.sort.direction as Direction, }; - if (!isEqual(newTlsSort, sort)) { + if (!deepEqual(newTlsSort, sort)) { updateNetworkTable({ networkType: type, tableType, @@ -103,10 +103,12 @@ const TlsTableComponent = React.memo( [sort, type, tableType, updateNetworkTable] ); + const columns = useMemo(() => getTlsColumns(tlsTableId), [tlsTableId]); + return ( ( field: getSortFromString(splitField[splitField.length - 1]), direction: criteria.sort.direction as Direction, }; - if (!isEqual(newUsersSort, sort)) { + if (!deepEqual(newUsersSort, sort)) { updateNetworkTable({ networkType: type, tableType, @@ -109,10 +109,15 @@ const UsersTableComponent = React.memo( [sort, type, updateNetworkTable] ); + const columns = useMemo(() => getUsersColumns(flowTarget, usersTableId), [ + flowTarget, + usersTableId, + ]); + return ( = ({ data, loading ); }; -OverviewHostStatsComponent.displayName = 'OverviewHostStatsComponent'; - export const OverviewHostStats = React.memo(OverviewHostStatsComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx index 260b1d6895140..ca947c29bc382 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.tsx @@ -130,7 +130,7 @@ const AccordionContent = styled.div` margin-top: 8px; `; -export const OverviewNetworkStats = React.memo(({ data, loading }) => { +const OverviewNetworkStatsComponent: React.FC = ({ data, loading }) => { const allNetworkStats = getOverviewNetworkStats(data); const allNetworkStatsCount = allNetworkStats.reduce((total, stat) => total + stat.count, 0); @@ -190,6 +190,6 @@ export const OverviewNetworkStats = React.memo(({ data, lo })} ); -}); +}; -OverviewNetworkStats.displayName = 'OverviewNetworkStats'; +export const OverviewNetworkStats = React.memo(OverviewNetworkStatsComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx index 03a8143c89517..557d389aefee9 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import React, { memo, useState, useEffect, useMemo, useCallback } from 'react'; +import deepEqual from 'fast-deep-equal'; import { Filter, @@ -64,7 +64,7 @@ export const QueryBar = memo( const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { - if (payload.query != null && !isEqual(payload.query, filterQuery)) { + if (payload.query != null && !deepEqual(payload.query, filterQuery)) { onSubmitQuery(payload.query); } }, @@ -73,7 +73,7 @@ export const QueryBar = memo( const onQueryChange = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { - if (payload.query != null && !isEqual(payload.query, draftQuery)) { + if (payload.query != null && !deepEqual(payload.query, draftQuery)) { setDraftQuery(payload.query); onChangedQuery(payload.query); } diff --git a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx index cb5729ad8e26e..2513004af84dd 100644 --- a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr, isEqual, set } from 'lodash/fp'; +import { getOr, set } from 'lodash/fp'; import React, { memo, useEffect, useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Dispatch } from 'redux'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { FilterManager, IIndexPattern, TimeRange, Query, Filter } from 'src/plugins/data/public'; import { SavedQuery } from 'src/legacy/core_plugins/data/public'; @@ -60,7 +61,6 @@ const SearchBarComponent = memo( setSavedQuery, setSearchBarFilter, start, - timelineId, toStr, updateSearch, dataTestSubj, @@ -108,7 +108,7 @@ const SearchBarComponent = memo( updateSearchBar.start = payload.dateRange.from; } - if (payload.query != null && !isEqual(payload.query, filterQuery)) { + if (payload.query != null && !deepEqual(payload.query, filterQuery)) { isStateUpdated = true; updateSearchBar = set('query', payload.query, updateSearchBar); } diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx index 33159387214e4..b8192cce11e5a 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx @@ -5,8 +5,9 @@ */ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { isEmpty, isEqual, uniqWith } from 'lodash/fp'; +import { isEmpty, uniqWith } from 'lodash/fp'; import React from 'react'; +import deepEqual from 'fast-deep-equal'; import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../ip'; import { DESTINATION_PORT_FIELD_NAME, SOURCE_PORT_FIELD_NAME, Port } from '../port'; @@ -115,7 +116,7 @@ const IpAdressesWithPorts = React.memo<{ return ( - {uniqWith(isEqual, ipPortPairs).map( + {uniqWith(deepEqual, ipPortPairs).map( ipPortPair => ipPortPair.ip != null && ( diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap index 69596ba8f3325..ef077ece19f92 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap @@ -94,7 +94,6 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] = isInspected={false} loading={false} queryId="statItems" - refetch={null} selectedInspectIndex={0} setIsInspected={[Function]} title="KPI HOSTS" @@ -328,7 +327,6 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] = isInspected={false} loading={false} queryId="statItems" - refetch={null} selectedInspectIndex={0} setIsInspected={[Function]} title="KPI HOSTS" @@ -632,7 +630,6 @@ exports[`Stat Items Component rendering kpis with charts it renders the default isInspected={false} loading={false} queryId="statItems" - refetch={null} selectedInspectIndex={0} setIsInspected={[Function]} title="KPI UNIQUE_PRIVATE_IPS" diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap index 3fcd258b79147..372930ee3167d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/__snapshots__/timeline.test.tsx.snap @@ -8,7 +8,7 @@ exports[`Timeline rendering renders correctly against snapshot 1`] = ` justifyContent="flexStart" > - = ({ browserFields, id, indexPattern, @@ -60,7 +60,7 @@ export const TimelineHeaderComponent = ({ onToggleDataProviderExcluded, show, showCallOutUnauthorizedMsg, -}: Props) => ( +}) => ( {showCallOutUnauthorizedMsg && ( ); -TimelineHeaderComponent.displayName = 'TimelineHeaderComponent'; - export const TimelineHeader = React.memo(TimelineHeaderComponent); - -TimelineHeader.displayName = 'TimelineHeader'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx index d782d0366f041..0ce6bc16f1325 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import React, { useEffect, useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { WithSource } from '../../containers/source'; import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; @@ -215,11 +215,11 @@ const StatefulTimelineComponent = React.memo( prevProps.show === nextProps.show && prevProps.showCallOutUnauthorizedMsg === nextProps.showCallOutUnauthorizedMsg && prevProps.start === nextProps.start && - isEqual(prevProps.columns, nextProps.columns) && - isEqual(prevProps.dataProviders, nextProps.dataProviders) && - isEqual(prevProps.filters, nextProps.filters) && - isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && - isEqual(prevProps.sort, nextProps.sort) + deepEqual(prevProps.columns, nextProps.columns) && + deepEqual(prevProps.dataProviders, nextProps.dataProviders) && + deepEqual(prevProps.filters, nextProps.filters) && + deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && + deepEqual(prevProps.sort, nextProps.sort) ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx index 96b8df6d8ada7..7f662cdb2f1b4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/query_bar/index.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual, isEmpty } from 'lodash/fp'; +import { isEmpty } from 'lodash/fp'; import React, { memo, useCallback, useState, useEffect } from 'react'; import { Subscription } from 'rxjs'; +import deepEqual from 'fast-deep-equal'; import { IIndexPattern, @@ -127,7 +128,7 @@ export const QueryBarTimeline = memo( const filterWithoutDropArea = filterManager .getFilters() .filter((f: Filter) => f.meta.controlledBy !== timelineFilterDropArea); - if (!isEqual(filters, filterWithoutDropArea)) { + if (!deepEqual(filters, filterWithoutDropArea)) { filterManager.setFilters(filters); } }, [filters]); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx index d1904fd5d9aac..87061bdbb5d02 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr, isEqual } from 'lodash/fp'; +import { getOr } from 'lodash/fp'; import React, { useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Dispatch } from 'redux'; +import deepEqual from 'fast-deep-equal'; import { Filter, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; import { BrowserFields } from '../../../containers/source'; @@ -152,15 +153,15 @@ const StatefulSearchOrFilterComponent = React.memo( prevProps.isRefreshPaused === nextProps.isRefreshPaused && prevProps.refreshInterval === nextProps.refreshInterval && prevProps.timelineId === nextProps.timelineId && - isEqual(prevProps.browserFields, nextProps.browserFields) && - isEqual(prevProps.dataProviders, nextProps.dataProviders) && - isEqual(prevProps.filters, nextProps.filters) && - isEqual(prevProps.filterQuery, nextProps.filterQuery) && - isEqual(prevProps.filterQueryDraft, nextProps.filterQueryDraft) && - isEqual(prevProps.indexPattern, nextProps.indexPattern) && - isEqual(prevProps.kqlMode, nextProps.kqlMode) && - isEqual(prevProps.savedQueryId, nextProps.savedQueryId) && - isEqual(prevProps.timelineId, nextProps.timelineId) + deepEqual(prevProps.browserFields, nextProps.browserFields) && + deepEqual(prevProps.dataProviders, nextProps.dataProviders) && + deepEqual(prevProps.filters, nextProps.filters) && + deepEqual(prevProps.filterQuery, nextProps.filterQuery) && + deepEqual(prevProps.filterQueryDraft, nextProps.filterQueryDraft) && + deepEqual(prevProps.indexPattern, nextProps.indexPattern) && + deepEqual(prevProps.kqlMode, nextProps.kqlMode) && + deepEqual(prevProps.savedQueryId, nextProps.savedQueryId) && + deepEqual(prevProps.timelineId, nextProps.timelineId) ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index c9ff0296a40e2..58bbbef328ddf 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -229,8 +229,4 @@ export const TimelineComponent: React.FC = ({ ); }; -TimelineComponent.displayName = 'TimelineComponent'; - export const Timeline = React.memo(TimelineComponent); - -Timeline.displayName = 'Timeline'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx index e656ec3496d8d..294e41a1faa7b 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import React from 'react'; import { compose, Dispatch } from 'redux'; import { connect } from 'react-redux'; +import deepEqual from 'fast-deep-equal'; import { timelineActions } from '../../store/actions'; import { RouteSpyState } from '../../utils/route/types'; @@ -39,7 +39,7 @@ export const UrlStateRedux = compose - prevProps.pathName === nextProps.pathName && isEqual(prevProps.urlState, nextProps.urlState) + prevProps.pathName === nextProps.pathName && deepEqual(prevProps.urlState, nextProps.urlState) ) ); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx index deaf9bbf5011d..a7704e0e86970 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/use_url_state.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual, difference, isEmpty } from 'lodash/fp'; +import { difference, isEmpty } from 'lodash/fp'; import { useEffect, useRef, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; import { useKibana } from '../../lib/kibana'; import { useApolloClient } from '../../utils/apollo_context'; @@ -77,7 +78,7 @@ export const useUrlStateHooks = ({ const updatedUrlStateString = getParamFromQueryString(getQueryStringFromLocation(mySearch), urlKey) ?? newUrlStateString; - if (isInitializing || !isEqual(updatedUrlStateString, newUrlStateString)) { + if (isInitializing || !deepEqual(updatedUrlStateString, newUrlStateString)) { urlStateToUpdate = [ ...urlStateToUpdate, { @@ -157,7 +158,7 @@ export const useUrlStateHooks = ({ if (isInitializing && pageName != null && pageName !== '') { handleInitialize(type); setIsInitializing(false); - } else if (!isEqual(urlState, prevProps.urlState) && !isInitializing) { + } else if (!deepEqual(urlState, prevProps.urlState) && !isInitializing) { let mySearch = search; URL_STATE_KEYS[type].forEach((urlKey: KeyUrlState) => { if ( diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx index b7ad41b8ba1bb..06c4d1054bca4 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/fetch_index_patterns.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty, isEqual, get } from 'lodash/fp'; +import { isEmpty, get } from 'lodash/fp'; import { useEffect, useState, Dispatch, SetStateAction } from 'react'; +import deepEqual from 'fast-deep-equal'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; import { @@ -41,7 +42,7 @@ export const useFetchIndexPatterns = (defaultIndices: string[] = []): Return => const [, dispatchToaster] = useStateToaster(); useEffect(() => { - if (!isEqual(defaultIndices, indices)) { + if (!deepEqual(defaultIndices, indices)) { setIndices(defaultIndices); } }, [defaultIndices, indices]); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx index b369d3a50730d..242d715e20f77 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx @@ -5,9 +5,8 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useRules, ReturnRules } from './use_rules'; +import { useRules, UseRules, ReturnRules } from './use_rules'; import * as api from './api'; -import { PaginationOptions, FilterOptions } from '.'; jest.mock('./api'); @@ -17,55 +16,40 @@ describe('useRules', () => { }); test('init', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook< - [PaginationOptions, FilterOptions], - ReturnRules - >(props => - useRules( - { + const { result, waitForNextUpdate } = renderHook(props => + useRules({ + pagination: { page: 1, perPage: 10, total: 100, }, - { + filterOptions: { filter: '', sortField: 'created_at', sortOrder: 'desc', - } - ) + }, + }) ); await waitForNextUpdate(); - expect(result.current).toEqual([ - true, - { - data: [], - page: 1, - perPage: 20, - total: 0, - }, - null, - ]); + expect(result.current).toEqual([true, null, result.current[2]]); }); }); test('fetch rules', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook< - [PaginationOptions, FilterOptions], - ReturnRules - >(() => - useRules( - { + const { result, waitForNextUpdate } = renderHook(() => + useRules({ + pagination: { page: 1, perPage: 10, total: 100, }, - { + filterOptions: { filter: '', sortField: 'created_at', sortOrder: 'desc', - } - ) + }, + }) ); await waitForNextUpdate(); await waitForNextUpdate(); @@ -148,22 +132,19 @@ describe('useRules', () => { test('re-fetch rules', async () => { const spyOnfetchRules = jest.spyOn(api, 'fetchRules'); await act(async () => { - const { result, waitForNextUpdate } = renderHook< - [PaginationOptions, FilterOptions], - ReturnRules - >(id => - useRules( - { + const { result, waitForNextUpdate } = renderHook(id => + useRules({ + pagination: { page: 1, perPage: 10, total: 100, }, - { + filterOptions: { filter: '', sortField: 'created_at', sortOrder: 'desc', - } - ) + }, + }) ); await waitForNextUpdate(); await waitForNextUpdate(); @@ -178,37 +159,37 @@ describe('useRules', () => { test('fetch rules if props changes', async () => { const spyOnfetchRules = jest.spyOn(api, 'fetchRules'); await act(async () => { - const { rerender, waitForNextUpdate } = renderHook< - [PaginationOptions, FilterOptions], - ReturnRules - >(args => useRules(args[0], args[1]), { - initialProps: [ - { - page: 1, - perPage: 10, - total: 100, - }, - { - filter: '', - sortField: 'created_at', - sortOrder: 'desc', + const { rerender, waitForNextUpdate } = renderHook( + args => useRules(args), + { + initialProps: { + pagination: { + page: 1, + perPage: 10, + total: 100, + }, + filterOptions: { + filter: '', + sortField: 'created_at', + sortOrder: 'desc', + }, }, - ], - }); + } + ); await waitForNextUpdate(); await waitForNextUpdate(); - rerender([ - { + rerender({ + pagination: { page: 1, perPage: 10, total: 100, }, - { + filterOptions: { filter: 'hello world', sortField: 'created_at', sortOrder: 'desc', }, - ]); + }); await waitForNextUpdate(); expect(spyOnfetchRules).toHaveBeenCalledTimes(2); }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index 301a68dc6f445..d05d59d15802d 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -4,16 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { noop } from 'lodash/fp'; import { useEffect, useState, useRef } from 'react'; -import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; +import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from './types'; import { useStateToaster } from '../../../components/toasters'; import { fetchRules } from './api'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import * as i18n from './translations'; -type Func = () => void; -export type ReturnRules = [boolean, FetchRulesResponse, Func | null]; +export type ReturnRules = [ + boolean, + FetchRulesResponse | null, + (refreshPrePackagedRule?: boolean) => void +]; + +export interface UseRules { + pagination: PaginationOptions; + filterOptions: FilterOptions; + refetchPrePackagedRulesStatus?: () => void; + dispatchRulesInReducer?: (rules: Rule[]) => void; +} /** * Hook for using the list of Rules from the Detection Engine API @@ -21,17 +32,14 @@ export type ReturnRules = [boolean, FetchRulesResponse, Func | null]; * @param pagination desired pagination options (e.g. page/perPage) * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) */ -export const useRules = ( - pagination: PaginationOptions, - filterOptions: FilterOptions -): ReturnRules => { - const [rules, setRules] = useState({ - page: 1, - perPage: 20, - total: 0, - data: [], - }); - const reFetchRules = useRef(null); +export const useRules = ({ + pagination, + filterOptions, + refetchPrePackagedRulesStatus, + dispatchRulesInReducer, +}: UseRules): ReturnRules => { + const [rules, setRules] = useState(null); + const reFetchRules = useRef<(refreshPrePackagedRule?: boolean) => void>(noop); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); @@ -50,10 +58,16 @@ export const useRules = ( if (isSubscribed) { setRules(fetchRulesResult); + if (dispatchRulesInReducer != null) { + dispatchRulesInReducer(fetchRulesResult.data); + } } } catch (error) { if (isSubscribed) { errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster }); + if (dispatchRulesInReducer != null) { + dispatchRulesInReducer([]); + } } } if (isSubscribed) { @@ -62,7 +76,12 @@ export const useRules = ( } fetchData(); - reFetchRules.current = fetchData.bind(null, true); + reFetchRules.current = (refreshPrePackagedRule: boolean = false) => { + fetchData(true); + if (refreshPrePackagedRule && refetchPrePackagedRulesStatus != null) { + refetchPrePackagedRulesStatus(); + } + }; return () => { isSubscribed = false; abortCtrl.abort(); @@ -76,6 +95,7 @@ export const useRules = ( filterOptions.tags?.sort().join(), filterOptions.showCustomRules, filterOptions.showElasticRules, + refetchPrePackagedRulesStatus, ]); return [loading, rules, reFetchRules.current]; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.test.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.test.tsx index 4a796efa5b0cb..68f54b35754f6 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.test.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.test.tsx @@ -14,7 +14,7 @@ describe('useTags', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useTags()); await waitForNextUpdate(); - expect(result.current).toEqual([true, []]); + expect(result.current).toEqual([true, [], result.current[2]]); }); }); @@ -23,7 +23,11 @@ describe('useTags', () => { const { result, waitForNextUpdate } = renderHook(() => useTags()); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual([false, ['elastic', 'love', 'quality', 'code']]); + expect(result.current).toEqual([ + false, + ['elastic', 'love', 'quality', 'code'], + result.current[2], + ]); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx index 196d4b1420561..5985200fa16ec 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { noop } from 'lodash/fp'; +import { useEffect, useState, useRef } from 'react'; import { useStateToaster } from '../../../components/toasters'; import { fetchTags } from './api'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import * as i18n from './translations'; -export type ReturnTags = [boolean, string[]]; +export type ReturnTags = [boolean, string[], () => void]; /** * Hook for using the list of Tags from the Detection Engine API @@ -20,6 +21,7 @@ export const useTags = (): ReturnTags => { const [tags, setTags] = useState([]); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); + const reFetchTags = useRef<() => void>(noop); useEffect(() => { let isSubscribed = true; @@ -46,6 +48,7 @@ export const useTags = (): ReturnTags => { } fetchData(); + reFetchTags.current = fetchData; return () => { isSubscribed = false; @@ -53,5 +56,5 @@ export const useTags = (): ReturnTags => { }; }, []); - return [loading, tags]; + return [loading, tags, reFetchTags.current]; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx index caf597d02c835..4632e9aee3fdd 100644 --- a/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect } from 'react'; +import React, { useCallback, useState, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { inputsModel, inputsSelectors, State } from '../../store'; @@ -41,6 +41,17 @@ export const GlobalTimeComponent: React.FC = ({ }) => { const [isInitializing, setIsInitializing] = useState(true); + const setQuery = useCallback( + ({ id, inspect, loading, refetch }: SetQuery) => + setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }), + [setGlobalQuery] + ); + + const deleteQuery = useCallback( + ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }), + [deleteOneQuery] + ); + useEffect(() => { if (isInitializing) { setIsInitializing(false); @@ -56,9 +67,8 @@ export const GlobalTimeComponent: React.FC = ({ isInitializing, from, to, - setQuery: ({ id, inspect, loading, refetch }: SetQuery) => - setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }), - deleteQuery: ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }), + setQuery, + deleteQuery, })} ); diff --git a/x-pack/legacy/plugins/siem/public/containers/query_template_paginated.tsx b/x-pack/legacy/plugins/siem/public/containers/query_template_paginated.tsx index 4d6ab757fdea7..db618f216d83e 100644 --- a/x-pack/legacy/plugins/siem/public/containers/query_template_paginated.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/query_template_paginated.tsx @@ -5,10 +5,10 @@ */ import { ApolloQueryResult, NetworkStatus } from 'apollo-client'; -import { isEqual } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import React from 'react'; import { FetchMoreOptions, FetchMoreQueryOptions, OperationVariables } from 'react-apollo'; +import deepEqual from 'fast-deep-equal'; import { ESQuery } from '../../common/typed_json'; import { inputsModel } from '../store/model'; @@ -85,7 +85,7 @@ export class QueryTemplatePaginated< public isItAValidLoading(loading: boolean, variables: TVariables, networkStatus: NetworkStatus) { if ( !this.myLoading && - (!isEqual(variables, this.queryVariables) || networkStatus === NetworkStatus.refetch) && + (!deepEqual(variables, this.queryVariables) || networkStatus === NetworkStatus.refetch) && loading ) { this.myLoading = true; diff --git a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx index 0336e4a9a977b..e454421ca955d 100644 --- a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx @@ -10,6 +10,7 @@ import { Query } from 'react-apollo'; import React, { useEffect, useMemo, useState } from 'react'; import memoizeOne from 'memoize-one'; import { IIndexPattern } from 'src/plugins/data/public'; + import { useUiSetting$ } from '../../lib/kibana'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx index 92f6740e4d767..2d9b1ee844b4b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram.tsx @@ -12,9 +12,11 @@ import { HistogramBarSeries, Position, Settings, + ChartSizeArray, } from '@elastic/charts'; -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiProgress } from '@elastic/eui'; + import { useTheme } from '../../../../components/charts/common'; import { histogramDateTimeFormatter } from '../../../../components/utils'; import { HistogramData } from './types'; @@ -43,6 +45,14 @@ export const SignalsHistogram = React.memo( }) => { const theme = useTheme(); + const chartSize: ChartSizeArray = useMemo(() => ['100%', chartHeight], [chartHeight]); + const xAxisId = useMemo(() => getAxisId('signalsHistogramAxisX'), []); + const yAxisId = useMemo(() => getAxisId('signalsHistogramAxisY'), []); + const id = useMemo(() => getSpecId('signalsHistogram'), []); + const yAccessors = useMemo(() => ['y'], []); + const splitSeriesAccessors = useMemo(() => ['g'], []); + const tickFormat = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); + return ( <> {loading && ( @@ -54,7 +64,7 @@ export const SignalsHistogram = React.memo( /> )} - + ( theme={theme} /> - + - + @@ -84,4 +90,5 @@ export const SignalsHistogram = React.memo( ); } ); + SignalsHistogram.displayName = 'SignalsHistogram'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts index 980575f1470a5..e2287e5eeeb3f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -5,7 +5,6 @@ */ import { Rule, RuleError } from '../../../../../containers/detection_engine/rules'; -import { TableData } from '../../types'; export const mockRule = (id: string): Rule => ({ created_at: '2020-01-10T21:11:45.839Z', @@ -50,103 +49,3 @@ export const mockRules: Rule[] = [ mockRule('abe6c564-050d-45a5-aaf0-386c37dd1f61'), mockRule('63f06f34-c181-4b2d-af35-f2ace572a1ee'), ]; -export const mockTableData: TableData[] = [ - { - activate: true, - id: 'abe6c564-050d-45a5-aaf0-386c37dd1f61', - immutable: false, - isLoading: false, - risk_score: 21, - rule: { - href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', - name: 'Home Grown!', - }, - rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', - severity: 'low', - sourceRule: { - created_at: '2020-01-10T21:11:45.839Z', - created_by: 'elastic', - description: '24/7', - enabled: true, - false_positives: [], - filters: [], - from: 'now-300s', - id: 'abe6c564-050d-45a5-aaf0-386c37dd1f61', - immutable: false, - index: ['auditbeat-*'], - interval: '5m', - language: 'kuery', - max_signals: 100, - meta: { from: '0m' }, - name: 'Home Grown!', - output_index: '.siem-signals-default', - query: '', - references: [], - risk_score: 21, - rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', - saved_id: "Garrett's IP", - severity: 'low', - tags: [], - threat: [], - timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', - timeline_title: 'Untitled timeline', - to: 'now', - type: 'saved_query', - updated_at: '2020-01-10T21:11:45.839Z', - updated_by: 'elastic', - version: 1, - }, - status: null, - statusDate: null, - tags: [], - }, - { - activate: true, - id: '63f06f34-c181-4b2d-af35-f2ace572a1ee', - immutable: false, - isLoading: false, - risk_score: 21, - rule: { - href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', - name: 'Home Grown!', - }, - rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', - severity: 'low', - sourceRule: { - created_at: '2020-01-10T21:11:45.839Z', - created_by: 'elastic', - description: '24/7', - enabled: true, - false_positives: [], - filters: [], - from: 'now-300s', - id: '63f06f34-c181-4b2d-af35-f2ace572a1ee', - immutable: false, - index: ['auditbeat-*'], - interval: '5m', - language: 'kuery', - max_signals: 100, - meta: { from: '0m' }, - name: 'Home Grown!', - output_index: '.siem-signals-default', - query: '', - references: [], - risk_score: 21, - rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', - saved_id: "Garrett's IP", - severity: 'low', - tags: [], - threat: [], - timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', - timeline_title: 'Untitled timeline', - to: 'now', - type: 'saved_query', - updated_at: '2020-01-10T21:11:45.839Z', - updated_by: 'elastic', - version: 1, - }, - status: null, - statusDate: null, - tags: [], - }, -]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index 6212c2067384d..a17fd34d1c344 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -32,53 +32,58 @@ export const editRuleAction = (rule: Rule, history: H.History) => { export const duplicateRulesAction = async ( rules: Rule[], + ruleIds: string[], dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { try { - const ruleIds = rules.map(r => r.id); - dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: true }); - const duplicatedRules = await duplicateRules({ rules }); - dispatch({ type: 'refresh' }); - displaySuccessToast( - i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRules.length), - dispatchToaster - ); + dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'duplicate' }); + const response = await duplicateRules({ rules }); + const { errors } = bucketRulesResponse(response); + if (errors.length > 0) { + displayErrorToast( + i18n.DUPLICATE_RULE_ERROR, + errors.map(e => e.error.message), + dispatchToaster + ); + } else { + displaySuccessToast(i18n.SUCCESSFULLY_DUPLICATED_RULES(ruleIds.length), dispatchToaster); + } + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); } catch (e) { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); displayErrorToast(i18n.DUPLICATE_RULE_ERROR, [e.message], dispatchToaster); } }; -export const exportRulesAction = async (rules: Rule[], dispatch: React.Dispatch) => { - dispatch({ type: 'setExportPayload', exportPayload: rules }); +export const exportRulesAction = (exportRuleId: string[], dispatch: React.Dispatch) => { + dispatch({ type: 'exportRuleIds', ids: exportRuleId }); }; export const deleteRulesAction = async ( - ids: string[], + ruleIds: string[], dispatch: React.Dispatch, dispatchToaster: Dispatch, onRuleDeleted?: () => void ) => { try { - dispatch({ type: 'loading', isLoading: true }); - - const response = await deleteRules({ ids }); + dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'delete' }); + const response = await deleteRules({ ids: ruleIds }); const { errors } = bucketRulesResponse(response); - - dispatch({ type: 'refresh' }); + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); if (errors.length > 0) { displayErrorToast( - i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), + i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ruleIds.length), errors.map(e => e.error.message), dispatchToaster ); - } else { - // FP: See https://github.com/typescript-eslint/typescript-eslint/issues/1138#issuecomment-566929566 - onRuleDeleted?.(); // eslint-disable-line no-unused-expressions + } else if (onRuleDeleted) { + onRuleDeleted(); } } catch (e) { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); displayErrorToast( - i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), + i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ruleIds.length), [e.message], dispatchToaster ); @@ -96,7 +101,7 @@ export const enableRulesAction = async ( : i18n.BATCH_ACTION_DEACTIVATE_SELECTED_ERROR(ids.length); try { - dispatch({ type: 'updateLoading', ids, isLoading: true }); + dispatch({ type: 'loadingRuleIds', ids, actionType: enabled ? 'enable' : 'disable' }); const response = await enableRules({ ids, enabled }); const { rules, errors } = bucketRulesResponse(response); @@ -125,6 +130,6 @@ export const enableRulesAction = async ( } } catch (e) { displayErrorToast(errorTitle, [e.message], dispatchToaster); - dispatch({ type: 'updateLoading', ids, isLoading: false }); + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); } }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index 8a10d4f7100b9..a0942d7f6534a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -6,9 +6,7 @@ import { EuiContextMenuItem } from '@elastic/eui'; import React, { Dispatch } from 'react'; -import * as H from 'history'; import * as i18n from '../translations'; -import { TableData } from '../types'; import { Action } from './reducer'; import { deleteRulesAction, @@ -17,18 +15,37 @@ import { exportRulesAction, } from './actions'; import { ActionToaster } from '../../../../components/toasters'; +import { Rule } from '../../../../containers/detection_engine/rules'; -export const getBatchItems = ( - selectedState: TableData[], - dispatch: Dispatch, - dispatchToaster: Dispatch, - history: H.History, - closePopover: () => void -) => { - const containsEnabled = selectedState.some(v => v.activate); - const containsDisabled = selectedState.some(v => !v.activate); - const containsLoading = selectedState.some(v => v.isLoading); - const containsImmutable = selectedState.some(v => v.immutable); +interface GetBatchItems { + closePopover: () => void; + dispatch: Dispatch; + dispatchToaster: Dispatch; + loadingRuleIds: string[]; + reFetchRules: (refreshPrePackagedRule?: boolean) => void; + rules: Rule[]; + selectedRuleIds: string[]; +} + +export const getBatchItems = ({ + closePopover, + dispatch, + dispatchToaster, + loadingRuleIds, + reFetchRules, + rules, + selectedRuleIds, +}: GetBatchItems) => { + const containsEnabled = selectedRuleIds.some( + id => rules.find(r => r.id === id)?.enabled ?? false + ); + const containsDisabled = selectedRuleIds.some( + id => !rules.find(r => r.id === id)?.enabled ?? false + ); + const containsLoading = selectedRuleIds.some(id => loadingRuleIds.includes(id)); + const containsImmutable = selectedRuleIds.some( + id => rules.find(r => r.id === id)?.immutable ?? false + ); return [ { closePopover(); - const deactivatedIds = selectedState.filter(s => !s.activate).map(s => s.id); + const deactivatedIds = selectedRuleIds.filter( + id => !rules.find(r => r.id === id)?.enabled ?? false + ); await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster); }} > @@ -49,7 +68,9 @@ export const getBatchItems = ( disabled={containsLoading || !containsEnabled} onClick={async () => { closePopover(); - const activatedIds = selectedState.filter(s => s.activate).map(s => s.id); + const activatedIds = selectedRuleIds.filter( + id => rules.find(r => r.id === id)?.enabled ?? false + ); await enableRulesAction(activatedIds, false, dispatch, dispatchToaster); }} > @@ -58,11 +79,11 @@ export const getBatchItems = ( { + disabled={containsImmutable || containsLoading || selectedRuleIds.length === 0} + onClick={() => { closePopover(); - await exportRulesAction( - selectedState.map(s => s.sourceRule), + exportRulesAction( + rules.filter(r => selectedRuleIds.includes(r.id)).map(r => r.rule_id), dispatch ); }} @@ -72,14 +93,16 @@ export const getBatchItems = ( { closePopover(); await duplicateRulesAction( - selectedState.map(s => s.sourceRule), + rules.filter(r => selectedRuleIds.includes(r.id)), + selectedRuleIds, dispatch, dispatchToaster ); + reFetchRules(true); }} > {i18n.BATCH_ACTION_DUPLICATE_SELECTED} @@ -88,14 +111,11 @@ export const getBatchItems = ( key={i18n.BATCH_ACTION_DELETE_SELECTED} icon="trash" title={containsImmutable ? i18n.BATCH_ACTION_DELETE_SELECTED_IMMUTABLE : undefined} - disabled={containsLoading || selectedState.length === 0} + disabled={containsLoading || selectedRuleIds.length === 0} onClick={async () => { closePopover(); - await deleteRulesAction( - selectedState.map(({ sourceRule: { id } }) => id), - dispatch, - dispatchToaster - ); + await deleteRulesAction(selectedRuleIds, dispatch, dispatchToaster); + reFetchRules(true); }} > {i18n.BATCH_ACTION_DELETE_SELECTED} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index d648854368c28..ff104f09d68ef 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -15,72 +15,92 @@ import { } from '@elastic/eui'; import * as H from 'history'; import React, { Dispatch } from 'react'; + +import { Rule } from '../../../../containers/detection_engine/rules'; import { getEmptyTagValue } from '../../../../components/empty_value'; +import { FormattedDate } from '../../../../components/formatted_date'; +import { getRuleDetailsUrl } from '../../../../components/link_to/redirect_to_detection_engine'; +import { ActionToaster } from '../../../../components/toasters'; +import { TruncatableText } from '../../../../components/truncatable_text'; +import { getStatusColor } from '../components/rule_status/helpers'; +import { RuleSwitch } from '../components/rule_switch'; +import { SeverityBadge } from '../components/severity_badge'; +import * as i18n from '../translations'; import { deleteRulesAction, duplicateRulesAction, editRuleAction, exportRulesAction, } from './actions'; - import { Action } from './reducer'; -import { TableData } from '../types'; -import * as i18n from '../translations'; -import { FormattedDate } from '../../../../components/formatted_date'; -import { RuleSwitch } from '../components/rule_switch'; -import { SeverityBadge } from '../components/severity_badge'; -import { ActionToaster } from '../../../../components/toasters'; -import { getStatusColor } from '../components/rule_status/helpers'; -import { TruncatableText } from '../../../../components/truncatable_text'; const getActions = ( dispatch: React.Dispatch, dispatchToaster: Dispatch, - history: H.History + history: H.History, + reFetchRules: (refreshPrePackagedRule?: boolean) => void ) => [ { description: i18n.EDIT_RULE_SETTINGS, icon: 'visControls', name: i18n.EDIT_RULE_SETTINGS, - onClick: (rowItem: TableData) => editRuleAction(rowItem.sourceRule, history), - enabled: (rowItem: TableData) => !rowItem.sourceRule.immutable, + onClick: (rowItem: Rule) => editRuleAction(rowItem, history), + enabled: (rowItem: Rule) => !rowItem.immutable, }, { description: i18n.DUPLICATE_RULE, icon: 'copy', name: i18n.DUPLICATE_RULE, - onClick: (rowItem: TableData) => - duplicateRulesAction([rowItem.sourceRule], dispatch, dispatchToaster), + onClick: (rowItem: Rule) => { + duplicateRulesAction([rowItem], [rowItem.id], dispatch, dispatchToaster); + reFetchRules(true); + }, }, { description: i18n.EXPORT_RULE, icon: 'exportAction', name: i18n.EXPORT_RULE, - onClick: (rowItem: TableData) => exportRulesAction([rowItem.sourceRule], dispatch), - enabled: (rowItem: TableData) => !rowItem.immutable, + onClick: (rowItem: Rule) => exportRulesAction([rowItem.rule_id], dispatch), + enabled: (rowItem: Rule) => !rowItem.immutable, }, { description: i18n.DELETE_RULE, icon: 'trash', name: i18n.DELETE_RULE, - onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), + onClick: (rowItem: Rule) => { + deleteRulesAction([rowItem.id], dispatch, dispatchToaster); + reFetchRules(true); + }, }, ]; -type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType; +type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType; + +interface GetColumns { + dispatch: React.Dispatch; + dispatchToaster: Dispatch; + history: H.History; + hasNoPermissions: boolean; + loadingRuleIds: string[]; + reFetchRules: (refreshPrePackagedRule?: boolean) => void; +} // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? -export const getColumns = ( - dispatch: React.Dispatch, - dispatchToaster: Dispatch, - history: H.History, - hasNoPermissions: boolean -): RulesColumns[] => { +export const getColumns = ({ + dispatch, + dispatchToaster, + history, + hasNoPermissions, + loadingRuleIds, + reFetchRules, +}: GetColumns): RulesColumns[] => { const cols: RulesColumns[] = [ { - field: 'rule', + field: 'name', name: i18n.COLUMN_RULE, - render: (value: TableData['rule']) => {value.name}, + render: (value: Rule['name'], item: Rule) => ( + {value} + ), truncateText: true, width: '24%', }, @@ -93,14 +113,14 @@ export const getColumns = ( { field: 'severity', name: i18n.COLUMN_SEVERITY, - render: (value: TableData['severity']) => , + render: (value: Rule['severity']) => , truncateText: true, width: '16%', }, { - field: 'statusDate', + field: 'status_date', name: i18n.COLUMN_LAST_COMPLETE_RUN, - render: (value: TableData['statusDate']) => { + render: (value: Rule['status_date']) => { return value == null ? ( getEmptyTagValue() ) : ( @@ -114,7 +134,7 @@ export const getColumns = ( { field: 'status', name: i18n.COLUMN_LAST_RESPONSE, - render: (value: TableData['status']) => { + render: (value: Rule['status']) => { return ( <> @@ -129,7 +149,7 @@ export const getColumns = ( { field: 'tags', name: i18n.COLUMN_TAGS, - render: (value: TableData['tags']) => ( + render: (value: Rule['tags']) => ( {value.map((tag, i) => ( @@ -145,13 +165,13 @@ export const getColumns = ( align: 'center', field: 'activate', name: i18n.COLUMN_ACTIVATE, - render: (value: TableData['activate'], item: TableData) => ( + render: (value: Rule['enabled'], item: Rule) => ( ), sortable: true, @@ -160,9 +180,9 @@ export const getColumns = ( ]; const actions: RulesColumns[] = [ { - actions: getActions(dispatch, dispatchToaster, history), + actions: getActions(dispatch, dispatchToaster, history, reFetchRules), width: '40px', - } as EuiTableActionsColumnType, + } as EuiTableActionsColumnType, ]; return hasNoPermissions ? cols : [...cols, ...actions]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx index e925161444e42..c60933733587d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { bucketRulesResponse, formatRules } from './helpers'; -import { mockRule, mockRuleError, mockRules, mockTableData } from './__mocks__/mock'; +import { bucketRulesResponse } from './helpers'; +import { mockRule, mockRuleError } from './__mocks__/mock'; import uuid from 'uuid'; import { Rule, RuleError } from '../../../../containers/detection_engine/rules'; @@ -15,20 +15,6 @@ describe('AllRulesTable Helpers', () => { const mockRuleError1: Readonly = mockRuleError(uuid.v4()); const mockRuleError2: Readonly = mockRuleError(uuid.v4()); - describe('formatRules', () => { - test('formats rules with no selection', () => { - const formattedRules = formatRules(mockRules); - expect(formattedRules).toEqual(mockTableData); - }); - - test('formats rules with selection', () => { - const mockTableDataWithSelected = [...mockTableData]; - mockTableDataWithSelected[0].isLoading = true; - const formattedRules = formatRules(mockRules, [mockRules[0].id]); - expect(formattedRules).toEqual(mockTableDataWithSelected); - }); - }); - describe('bucketRulesResponse', () => { test('buckets empty response', () => { const bucketedResponse = bucketRulesResponse([]); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index 9a523536694d9..5ce26144a4d9c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -9,32 +9,6 @@ import { RuleError, RuleResponseBuckets, } from '../../../../containers/detection_engine/rules'; -import { TableData } from '../types'; - -/** - * Formats rules into the correct format for the AllRulesTable - * - * @param rules as returned from the Rules API - * @param selectedIds ids of the currently selected rules - */ -export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] => - rules.map(rule => ({ - id: rule.id, - immutable: rule.immutable, - rule_id: rule.rule_id, - rule: { - href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`, - name: rule.name, - }, - risk_score: rule.risk_score, - severity: rule.severity, - tags: rule.tags ?? [], - activate: rule.enabled, - status: rule.status ?? null, - statusDate: rule.status_date ?? null, - sourceRule: rule, - isLoading: selectedIds?.includes(rule.id) ?? false, - })); /** * Separates rules/errors from bulk rules API response (create/update/delete) @@ -52,14 +26,11 @@ export const bucketRulesResponse = (response: Array) => ); export const showRulesTable = ({ - isInitialLoad, rulesCustomInstalled, rulesInstalled, }: { - isInitialLoad: boolean; rulesCustomInstalled: number | null; rulesInstalled: number | null; }) => - !isInitialLoad && - ((rulesCustomInstalled != null && rulesCustomInstalled > 0) || - (rulesInstalled != null && rulesInstalled > 0)); + (rulesCustomInstalled != null && rulesCustomInstalled > 0) || + (rulesInstalled != null && rulesInstalled > 0); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index b304d77f2e276..79fec526faf48 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -11,15 +11,16 @@ import { EuiLoadingContent, EuiSpacer, } from '@elastic/eui'; -import { isEmpty } from 'lodash/fp'; -import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; +import styled from 'styled-components'; import uuid from 'uuid'; import { useRules, CreatePreBuiltRules, FilterOptions, + Rule, } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../components/header_section'; import { @@ -36,35 +37,39 @@ import { PrePackagedRulesPrompt } from '../components/pre_packaged_rules/load_em import { RuleDownloader } from '../components/rule_downloader'; import { getPrePackagedRuleStatus } from '../helpers'; import * as i18n from '../translations'; -import { EuiBasicTableOnChange, TableData } from '../types'; +import { EuiBasicTableOnChange } from '../types'; import { getBatchItems } from './batch_actions'; import { getColumns } from './columns'; import { showRulesTable } from './helpers'; import { allRulesReducer, State } from './reducer'; import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; +// EuiBasicTable give me a hardtime with adding the ref attributes so I went the easy way +// after few hours of fight with typescript !!!! I lost :( +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const MyEuiBasicTable = styled(EuiBasicTable as any)`` as any; + const initialState: State = { - isLoading: true, - rules: [], - tableData: [], - selectedItems: [], - refreshToggle: true, - pagination: { - page: 1, - perPage: 20, - total: 0, - }, + exportRuleIds: [], filterOptions: { filter: '', sortField: 'enabled', sortOrder: 'desc', }, + loadingRuleIds: [], + loadingRulesAction: null, + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + rules: [], + selectedRuleIds: [], }; interface AllRulesProps { createPrePackagedRules: CreatePreBuiltRules | null; hasNoPermissions: boolean; - importCompleteToggle: boolean; loading: boolean; loadingCreatePrePackagedRules: boolean; refetchPrePackagedRulesStatus: () => void; @@ -72,7 +77,7 @@ interface AllRulesProps { rulesInstalled: number | null; rulesNotInstalled: number | null; rulesNotUpdated: number | null; - setRefreshRulesData: (refreshRule: () => void) => void; + setRefreshRulesData: (refreshRule: (refreshPrePackagedRule?: boolean) => void) => void; } /** @@ -87,7 +92,6 @@ export const AllRules = React.memo( ({ createPrePackagedRules, hasNoPermissions, - importCompleteToggle, loading, loadingCreatePrePackagedRules, refetchPrePackagedRulesStatus, @@ -97,24 +101,36 @@ export const AllRules = React.memo( rulesNotUpdated, setRefreshRulesData, }) => { + const [initLoading, setInitLoading] = useState(true); + const tableRef = useRef(); const [ { - exportPayload, + exportRuleIds, filterOptions, - isLoading, - refreshToggle, - selectedItems, - tableData, + loadingRuleIds, + loadingRulesAction, pagination, + rules, + selectedRuleIds, }, dispatch, - ] = useReducer(allRulesReducer, initialState); + ] = useReducer(allRulesReducer(tableRef), initialState); const history = useHistory(); - const [oldRefreshToggle, setOldRefreshToggle] = useState(refreshToggle); - const [isInitialLoad, setIsInitialLoad] = useState(true); - const [isGlobalLoading, setIsGlobalLoad] = useState(false); const [, dispatchToaster] = useStateToaster(); - const [isLoadingRules, rulesData, reFetchRulesData] = useRules(pagination, filterOptions); + + const setRules = useCallback((newRules: Rule[]) => { + dispatch({ + type: 'setRules', + rules: newRules, + }); + }, []); + + const [isLoadingRules, , reFetchRulesData] = useRules({ + pagination, + filterOptions, + refetchPrePackagedRulesStatus, + dispatchRulesInReducer: setRules, + }); const prePackagedRuleStatus = getPrePackagedRuleStatus( rulesInstalled, @@ -125,10 +141,18 @@ export const AllRules = React.memo( const getBatchItemsPopoverContent = useCallback( (closePopover: () => void) => ( ), - [selectedItems, dispatch, dispatchToaster, history] + [dispatch, dispatchToaster, loadingRuleIds, reFetchRulesData, rules, selectedRuleIds] ); const tableOnChangeCallback = useCallback( @@ -146,46 +170,19 @@ export const AllRules = React.memo( ); const columns = useMemo(() => { - return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); - }, [dispatch, dispatchToaster, history]); - - useEffect(() => { - dispatch({ type: 'loading', isLoading: isLoadingRules }); - }, [isLoadingRules]); - - useEffect(() => { - if (!isLoadingRules && !loading && isInitialLoad) { - setIsInitialLoad(false); - } - }, [isInitialLoad, isLoadingRules, loading]); - - useEffect(() => { - if (!isGlobalLoading && (isLoadingRules || isLoading)) { - setIsGlobalLoad(true); - } else if (isGlobalLoading && !isLoadingRules && !isLoading) { - setIsGlobalLoad(false); - } - }, [setIsGlobalLoad, isGlobalLoading, isLoadingRules, isLoading]); - - useEffect(() => { - if (!isInitialLoad) { - dispatch({ type: 'refresh' }); - } - }, [importCompleteToggle]); - - useEffect(() => { - if (!isInitialLoad && reFetchRulesData != null && oldRefreshToggle !== refreshToggle) { - setOldRefreshToggle(refreshToggle); - reFetchRulesData(); - refetchPrePackagedRulesStatus(); - } - }, [ - isInitialLoad, - refreshToggle, - oldRefreshToggle, - reFetchRulesData, - refetchPrePackagedRulesStatus, - ]); + return getColumns({ + dispatch, + dispatchToaster, + history, + hasNoPermissions, + loadingRuleIds: + loadingRulesAction != null && + (loadingRulesAction === 'enable' || loadingRulesAction === 'disable') + ? loadingRuleIds + : [], + reFetchRules: reFetchRulesData, + }); + }, [dispatch, dispatchToaster, history, loadingRuleIds, loadingRulesAction, reFetchRulesData]); useEffect(() => { if (reFetchRulesData != null) { @@ -194,31 +191,25 @@ export const AllRules = React.memo( }, [reFetchRulesData, setRefreshRulesData]); useEffect(() => { - dispatch({ - type: 'updateRules', - rules: rulesData.data, - pagination: { - page: rulesData.page, - perPage: rulesData.perPage, - total: rulesData.total, - }, - }); - }, [rulesData]); + if (initLoading && !loading && !isLoadingRules) { + setInitLoading(false); + } + }, [initLoading, loading, isLoadingRules]); const handleCreatePrePackagedRules = useCallback(async () => { - if (createPrePackagedRules != null) { + if (createPrePackagedRules != null && reFetchRulesData != null) { await createPrePackagedRules(); - dispatch({ type: 'refresh' }); + reFetchRulesData(true); } - }, [createPrePackagedRules]); + }, [createPrePackagedRules, reFetchRulesData]); const euiBasicTableSelectionProps = useMemo( () => ({ - selectable: (item: TableData) => !item.isLoading, - onSelectionChange: (selected: TableData[]) => - dispatch({ type: 'setSelected', selectedItems: selected }), + selectable: (item: Rule) => !loadingRuleIds.includes(item.id), + onSelectionChange: (selected: Rule[]) => + dispatch({ type: 'selectedRuleIds', ids: selected.map(r => r.id) }), }), - [] + [loadingRuleIds] ); const onFilterChangedCallback = useCallback((newFilterOptions: Partial) => { @@ -237,12 +228,25 @@ export const AllRules = React.memo( ); }, []); + const isLoadingAnActionOnRule = useMemo(() => { + if ( + loadingRuleIds.length > 0 && + (loadingRulesAction === 'disable' || loadingRulesAction === 'enable') + ) { + return false; + } else if (loadingRuleIds.length > 0) { + return true; + } + return false; + }, [loadingRuleIds, loadingRulesAction]); + return ( <> { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); dispatchToaster({ type: 'addToaster', toast: { @@ -256,22 +260,17 @@ export const AllRules = React.memo( /> - + <> - {((rulesCustomInstalled && rulesCustomInstalled > 0) || - (rulesInstalled != null && rulesInstalled > 0)) && ( - - - - )} - {isInitialLoad && ( - - )} - {isGlobalLoading && !isEmpty(tableData) && !isInitialLoad && ( + + + + + {(loading || isLoadingRules || isLoadingAnActionOnRule) && !initLoading && ( )} {rulesCustomInstalled != null && @@ -283,7 +282,10 @@ export const AllRules = React.memo( userHasNoPermissions={hasNoPermissions} /> )} - {showRulesTable({ isInitialLoad, rulesCustomInstalled, rulesInstalled }) && ( + {initLoading && ( + + )} + {showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading && ( <> @@ -292,7 +294,7 @@ export const AllRules = React.memo( - {i18n.SELECTED_RULES(selectedItems.length)} + {i18n.SELECTED_RULES(selectedRuleIds.length)} {!hasNoPermissions && ( ( )} dispatch({ type: 'refresh' })} + onClick={() => reFetchRulesData(true)} > {i18n.REFRESH} - - ( totalItemCount: pagination.total, pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], }} - sorting={{ sort: { field: 'activate', direction: filterOptions.sortOrder } }} + ref={tableRef} + sorting={{ sort: { field: 'enabled', direction: filterOptions.sortOrder } }} selection={hasNoPermissions ? undefined : euiBasicTableSelectionProps} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts index 3634a16cbf6ac..54da43efd66d9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts @@ -4,34 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiBasicTable } from '@elastic/eui'; import { FilterOptions, PaginationOptions, Rule, } from '../../../../containers/detection_engine/rules'; -import { TableData } from '../types'; -import { formatRules } from './helpers'; +type LoadingRuleAction = 'duplicate' | 'enable' | 'disable' | 'export' | 'delete' | null; export interface State { - isLoading: boolean; - rules: Rule[]; - selectedItems: TableData[]; - pagination: PaginationOptions; + exportRuleIds: string[]; filterOptions: FilterOptions; - refreshToggle: boolean; - tableData: TableData[]; - exportPayload?: Rule[]; + loadingRuleIds: string[]; + loadingRulesAction: LoadingRuleAction; + pagination: PaginationOptions; + rules: Rule[]; + selectedRuleIds: string[]; } export type Action = - | { type: 'refresh' } - | { type: 'loading'; isLoading: boolean } - | { type: 'deleteRules'; rules: Rule[] } - | { type: 'duplicate'; rule: Rule } - | { type: 'setExportPayload'; exportPayload?: Rule[] } - | { type: 'setSelected'; selectedItems: TableData[] } - | { type: 'updateLoading'; ids: string[]; isLoading: boolean } - | { type: 'updateRules'; rules: Rule[]; pagination?: PaginationOptions } + | { type: 'exportRuleIds'; ids: string[] } + | { type: 'loadingRuleIds'; ids: string[]; actionType: LoadingRuleAction } + | { type: 'selectedRuleIds'; ids: string[] } + | { type: 'setRules'; rules: Rule[] } + | { type: 'updateRules'; rules: Rule[] } | { type: 'updatePagination'; pagination: Partial } | { type: 'updateFilterOptions'; @@ -40,54 +36,71 @@ export type Action = } | { type: 'failure' }; -export const allRulesReducer = (state: State, action: Action): State => { +export const allRulesReducer = ( + tableRef: React.MutableRefObject | undefined> +) => (state: State, action: Action): State => { switch (action.type) { - case 'refresh': { + case 'exportRuleIds': { return { ...state, - refreshToggle: !state.refreshToggle, + loadingRuleIds: action.ids, + loadingRulesAction: 'export', + exportRuleIds: action.ids, }; } - case 'updateRules': { - // If pagination included, this was a hard refresh - if (action.pagination) { - return { - ...state, - rules: action.rules, - pagination: action.pagination, - tableData: formatRules(action.rules), - }; + case 'loadingRuleIds': { + return { + ...state, + loadingRuleIds: action.actionType == null ? [] : [...state.loadingRuleIds, ...action.ids], + loadingRulesAction: action.actionType, + }; + } + case 'selectedRuleIds': { + return { + ...state, + selectedRuleIds: action.ids, + }; + } + case 'setRules': { + if ( + tableRef != null && + tableRef.current != null && + tableRef.current.changeSelection != null + ) { + tableRef.current.changeSelection([]); } - const ruleIds = state.rules.map(r => r.rule_id); - const updatedRules = action.rules.reverse().reduce((rules, updatedRule) => { - let newRules = rules; - if (ruleIds.includes(updatedRule.rule_id)) { - newRules = newRules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)); - } else { - newRules = [...newRules, updatedRule]; - } - return newRules; - }, state.rules); - - // Update enabled on selectedItems so that batch actions show correct available actions - const updatedRuleIdToState = action.rules.reduce>( - (acc, r) => ({ ...acc, [r.id]: r.enabled }), - {} - ); - const updatedSelectedItems = state.selectedItems.map(selectedItem => - Object.keys(updatedRuleIdToState).includes(selectedItem.id) - ? { ...selectedItem, activate: updatedRuleIdToState[selectedItem.id] } - : selectedItem - ); - return { ...state, - rules: updatedRules, - tableData: formatRules(updatedRules), - selectedItems: updatedSelectedItems, + rules: action.rules, + selectedRuleIds: [], + loadingRuleIds: [], + loadingRulesAction: null, }; } + case 'updateRules': { + if (state.rules != null) { + const ruleIds = state.rules.map(r => r.id); + const updatedRules = action.rules.reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.id)) { + newRules = newRules.map(r => (updatedRule.id === r.id ? updatedRule : r)); + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); + const updatedRuleIds = action.rules.map(r => r.id); + const newLoadingRuleIds = state.loadingRuleIds.filter(id => !updatedRuleIds.includes(id)); + return { + ...state, + rules: updatedRules, + loadingRuleIds: newLoadingRuleIds, + loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, + }; + } + return state; + } case 'updatePagination': { return { ...state, @@ -110,51 +123,12 @@ export const allRulesReducer = (state: State, action: Action): State => { }, }; } - case 'deleteRules': { - const deletedRuleIds = action.rules.map(r => r.rule_id); - const updatedRules = state.rules.reduce( - (rules, rule) => (deletedRuleIds.includes(rule.rule_id) ? rules : [...rules, rule]), - [] - ); - return { - ...state, - rules: updatedRules, - tableData: formatRules(updatedRules), - refreshToggle: !state.refreshToggle, - }; - } - case 'setSelected': { - return { - ...state, - selectedItems: action.selectedItems, - }; - } - case 'updateLoading': { - return { - ...state, - rules: state.rules, - tableData: formatRules(state.rules, action.ids), - }; - } - case 'loading': { - return { - ...state, - isLoading: action.isLoading, - }; - } case 'failure': { return { ...state, - isLoading: false, rules: [], }; } - case 'setExportPayload': { - return { - ...state, - exportPayload: [...(action.exportPayload ?? [])], - }; - } default: return state; } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx index fa4f6a874ca5e..ddb8894c206b5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -41,7 +41,11 @@ const RulesTableFiltersComponent = ({ const [selectedTags, setSelectedTags] = useState([]); const [showCustomRules, setShowCustomRules] = useState(false); const [showElasticRules, setShowElasticRules] = useState(false); - const [isLoadingTags, tags] = useTags(); + const [isLoadingTags, tags, reFetchTags] = useTags(); + + useEffect(() => { + reFetchTags(); + }, [rulesCustomInstalled, rulesInstalled]); // Propagate filter changes to parent useEffect(() => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx index 88795f9195e68..fbe854c1ee346 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx @@ -5,10 +5,10 @@ */ import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; -import { isEqual } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { Filter, @@ -99,7 +99,7 @@ export const QueryBarDefineRule = ({ const newFilters = filterManager.getFilters(); const { filters } = field.value as FieldValueQueryBar; - if (!isEqual(filters, newFilters)) { + if (!deepEqual(filters, newFilters)) { field.setValue({ ...(field.value as FieldValueQueryBar), filters: newFilters }); } } @@ -117,10 +117,10 @@ export const QueryBarDefineRule = ({ let isSubscribed = true; async function updateFilterQueryFromValue() { const { filters, query, saved_id: savedId } = field.value as FieldValueQueryBar; - if (!isEqual(query, queryDraft)) { + if (!deepEqual(query, queryDraft)) { setQueryDraft(query); } - if (!isEqual(filters, filterManager.getFilters())) { + if (!deepEqual(filters, filterManager.getFilters())) { filterManager.setFilters(filters); } if ( @@ -148,7 +148,7 @@ export const QueryBarDefineRule = ({ const onSubmitQuery = useCallback( (newQuery: Query, timefilter?: SavedQueryTimeFilter) => { const { query } = field.value as FieldValueQueryBar; - if (!isEqual(query, newQuery)) { + if (!deepEqual(query, newQuery)) { field.setValue({ ...(field.value as FieldValueQueryBar), query: newQuery }); } }, @@ -158,7 +158,7 @@ export const QueryBarDefineRule = ({ const onChangedQuery = useCallback( (newQuery: Query) => { const { query } = field.value as FieldValueQueryBar; - if (!isEqual(query, newQuery)) { + if (!deepEqual(query, newQuery)) { field.setValue({ ...(field.value as FieldValueQueryBar), query: newQuery }); } }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap index 9bd2fab23ac99..9355d0ae2cccb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/__snapshots__/index.test.tsx.snap @@ -58,6 +58,7 @@ exports[`RuleActionsOverflow renders correctly against snapshot 1`] = ` `; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index b52c10881cf42..7c8926c2064c7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -48,7 +48,7 @@ const RuleActionsOverflowComponent = ({ userHasNoPermissions, }: RuleActionsOverflowComponentProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const [rulesToExport, setRulesToExport] = useState(undefined); + const [rulesToExport, setRulesToExport] = useState([]); const history = useHistory(); const [, dispatchToaster] = useStateToaster(); @@ -66,7 +66,7 @@ const RuleActionsOverflowComponent = ({ disabled={userHasNoPermissions} onClick={async () => { setIsPopoverOpen(false); - await duplicateRulesAction([rule], noop, dispatchToaster); + await duplicateRulesAction([rule], [rule.id], noop, dispatchToaster); }} > {i18nActions.DUPLICATE_RULE} @@ -75,9 +75,9 @@ const RuleActionsOverflowComponent = ({ key={i18nActions.EXPORT_RULE} icon="indexEdit" disabled={userHasNoPermissions || rule.immutable} - onClick={async () => { + onClick={() => { setIsPopoverOpen(false); - setRulesToExport([rule]); + setRulesToExport([rule.id]); }} > {i18nActions.EXPORT_RULE} @@ -131,7 +131,7 @@ const RuleActionsOverflowComponent = ({ { displaySuccessToast( i18nActions.SUCCESSFULLY_EXPORTED_RULES(exportCount), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_downloader/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_downloader/index.tsx index b41265adea6b1..5d3086051a6e2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_downloader/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_downloader/index.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useRef } from 'react'; import styled from 'styled-components'; import { isFunction } from 'lodash/fp'; -import { exportRules, Rule } from '../../../../../containers/detection_engine/rules'; +import { exportRules } from '../../../../../containers/detection_engine/rules'; import { displayErrorToast, useStateToaster } from '../../../../../components/toasters'; import * as i18n from './translations'; @@ -17,7 +17,7 @@ const InvisibleAnchor = styled.a` export interface RuleDownloaderProps { filename: string; - rules?: Rule[]; + ruleIds?: string[]; onExportComplete: (exportCount: number) => void; } @@ -30,7 +30,7 @@ export interface RuleDownloaderProps { */ export const RuleDownloaderComponent = ({ filename, - rules, + ruleIds, onExportComplete, }: RuleDownloaderProps) => { const anchorRef = useRef(null); @@ -41,10 +41,10 @@ export const RuleDownloaderComponent = ({ const abortCtrl = new AbortController(); async function exportData() { - if (anchorRef && anchorRef.current && rules != null) { + if (anchorRef && anchorRef.current && ruleIds != null && ruleIds.length > 0) { try { const exportResponse = await exportRules({ - ruleIds: rules.map(r => r.rule_id), + ruleIds, signal: abortCtrl.signal, }); @@ -61,7 +61,7 @@ export const RuleDownloaderComponent = ({ window.URL.revokeObjectURL(objectURL); } - onExportComplete(rules.length); + onExportComplete(ruleIds.length); } } catch (error) { if (isSubscribed) { @@ -77,7 +77,7 @@ export const RuleDownloaderComponent = ({ isSubscribed = false; abortCtrl.abort(); }; - }, [rules]); + }, [ruleIds]); return ; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx index 2c9173cbeb694..ac457d7345c29 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx @@ -12,8 +12,8 @@ import { EuiLoadingSpinner, EuiText, } from '@elastic/eui'; -import { isEqual } from 'lodash/fp'; import React, { memo, useCallback, useEffect, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; import { useRuleStatus, RuleInfoStatus } from '../../../../../containers/detection_engine/rules'; import { FormattedDate } from '../../../../../components/formatted_date'; @@ -43,7 +43,7 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) }, [fetchRuleStatus, myRuleEnabled, ruleId, ruleEnabled, setMyRuleEnabled]); useEffect(() => { - if (!isEqual(currentStatus, ruleStatus?.current_status)) { + if (!deepEqual(currentStatus, ruleStatus?.current_status)) { setCurrentStatus(ruleStatus?.current_status ?? null); } }, [currentStatus, ruleStatus, setCurrentStatus]); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index 45da7d081333e..431d793d6e68a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -13,9 +13,9 @@ import { EuiSpacer, EuiButtonEmpty, } from '@elastic/eui'; -import { isEqual } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { setFieldValue } from '../../helpers'; import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; @@ -103,7 +103,7 @@ const StepAboutRuleComponent: FC = ({ useEffect(() => { const { isNew, ...initDefaultValue } = myStepData; - if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + if (defaultValues != null && !deepEqual(initDefaultValue, defaultValues)) { const myDefaultValues = { ...defaultValues, isNew: false, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx index 920a9f2dfe56c..773eb44efb26c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx @@ -11,9 +11,10 @@ import { EuiFlexItem, EuiButton, } from '@elastic/eui'; -import { isEmpty, isEqual } from 'lodash/fp'; +import { isEmpty } from 'lodash/fp'; import React, { FC, memo, useCallback, useState, useEffect } from 'react'; import styled from 'styled-components'; +import deepEqual from 'fast-deep-equal'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules'; @@ -126,9 +127,9 @@ const StepDefineRuleComponent: FC = ({ useEffect(() => { if (indicesConfig != null && defaultValues != null) { const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); - if (!isEqual(myDefaultValues, myStepData)) { + if (!deepEqual(myDefaultValues, myStepData)) { setMyStepData(myDefaultValues); - setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig)); + setLocalUseIndicesConfig(deepEqual(myDefaultValues.index, indicesConfig)); setFieldValue(form, schema, myDefaultValues); } } @@ -212,13 +213,13 @@ const StepDefineRuleComponent: FC = ({ {({ index }) => { if (index != null) { - if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { + if (deepEqual(index, indicesConfig) && !localUseIndicesConfig) { setLocalUseIndicesConfig(true); } - if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { + if (!deepEqual(index, indicesConfig) && localUseIndicesConfig) { setLocalUseIndicesConfig(false); } - if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { + if (index != null && !isEmpty(index) && !deepEqual(index, mylocalIndicesConfig)) { setMyLocalIndicesConfig(index); } } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx index cfbb0a622c721..2e2c7e068dd85 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx @@ -5,8 +5,8 @@ */ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import { isEqual } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; import { setFieldValue } from '../../helpers'; import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types'; @@ -62,7 +62,7 @@ const StepScheduleRuleComponent: FC = ({ useEffect(() => { const { isNew, ...initDefaultValue } = myStepData; - if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + if (defaultValues != null && !deepEqual(initDefaultValue, defaultValues)) { const myDefaultValues = { ...defaultValues, isNew: false, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 0c53ad19a3574..20185c2eda816 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -26,11 +26,10 @@ import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/u import { getPrePackagedRuleStatus, redirectToDetections } from './helpers'; import * as i18n from './translations'; -type Func = () => void; +type Func = (refreshPrePackagedRule?: boolean) => void; const RulesPageComponent: React.FC = () => { const [showImportModal, setShowImportModal] = useState(false); - const [importCompleteToggle, setImportCompleteToggle] = useState(false); const refreshRulesData = useRef(null); const { loading, @@ -67,14 +66,18 @@ const RulesPageComponent: React.FC = () => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; + const handleRefreshRules = useCallback(async () => { + if (refreshRulesData.current != null) { + refreshRulesData.current(true); + } + }, [refreshRulesData]); + const handleCreatePrePackagedRules = useCallback(async () => { if (createPrePackagedRules != null) { await createPrePackagedRules(); - if (refreshRulesData.current != null) { - refreshRulesData.current(); - } + handleRefreshRules(); } - }, [createPrePackagedRules, refreshRulesData]); + }, [createPrePackagedRules, handleRefreshRules]); const handleRefetchPrePackagedRulesStatus = useCallback(() => { if (refetchPrePackagedRulesStatus != null) { @@ -96,7 +99,7 @@ const RulesPageComponent: React.FC = () => { setShowImportModal(false)} - importComplete={() => setImportCompleteToggle(!importCompleteToggle)} + importComplete={handleRefreshRules} /> { loading={loading || prePackagedRuleLoading} loadingCreatePrePackagedRules={loadingCreatePrePackagedRules} hasNoPermissions={userHasNoPermissions} - importCompleteToggle={importCompleteToggle} refetchPrePackagedRulesStatus={handleRefetchPrePackagedRulesStatus} rulesCustomInstalled={rulesCustomInstalled} rulesInstalled={rulesInstalled} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts index 55eb45fb5ed9d..b2650dcc2b77e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts @@ -5,7 +5,6 @@ */ import { Filter } from '../../../../../../../../src/plugins/data/common'; -import { Rule } from '../../../containers/detection_engine/rules'; import { FieldValueQueryBar } from './components/query_bar'; import { FormData, FormHook } from '../../shared_imports'; import { FieldValueTimeline } from './components/pick_timeline'; @@ -23,24 +22,6 @@ export interface EuiBasicTableOnChange { sort?: EuiBasicTableSortTypes; } -export interface TableData { - id: string; - immutable: boolean; - rule_id: string; - rule: { - href: string; - name: string; - }; - risk_score: number; - severity: string; - tags: string[]; - activate: boolean; - isLoading: boolean; - sourceRule: Rule; - status?: string | null; - statusDate?: string | null; -} - export enum RuleStep { defineRule = 'define-rule', aboutRule = 'about-rule', diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx index 41eb620850a7f..f5efd9248029d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx @@ -73,32 +73,25 @@ export const HostDetailsTabs = React.memo( return ( - } - /> - } - /> - } - /> - ( - - )} - /> - } - /> - } - /> + + + + + + + + + + + + + + + + + + + ); } diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.tsx index 0b83710a13293..80c35e5563c1d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo } from 'react'; +import React, { memo, useCallback } from 'react'; import { Route, Switch } from 'react-router-dom'; import { HostsTabsProps } from './types'; @@ -22,7 +22,7 @@ import { } from './navigation'; import { HostAlertsQueryTabBody } from './navigation/alerts_query_tab_body'; -const HostsTabs = memo( +export const HostsTabs = memo( ({ deleteQuery, filterQuery, @@ -44,49 +44,48 @@ const HostsTabs = memo( startDate: from, type, indexPattern, - narrowDateRange: (score: Anomaly, interval: string) => { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }, + narrowDateRange: useCallback( + (score: Anomaly, interval: string) => { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }, + [setAbsoluteRangeDatePicker] + ), + updateDateRange: useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ), }; return ( - } - /> - } - /> - } - /> - ( - - )} - /> - } - /> - } - /> + + + + + + + + + + + + + + + + + + ); } ); HostsTabs.displayName = 'HostsTabs'; - -export { HostsTabs }; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx b/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx index 23a619db97ee4..b6b54b68ac06a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/navigation/network_routes.tsx @@ -23,77 +23,82 @@ import { TlsQueryTabBody } from './tls_query_tab_body'; import { Anomaly } from '../../../components/ml/types'; import { NetworkAlertsQueryTabBody } from './alerts_query_tab_body'; -export const NetworkRoutes = ({ - networkPagePath, - type, - to, - filterQuery, - isInitializing, - from, - indexPattern, - setQuery, - setAbsoluteRangeDatePicker, -}: NetworkRoutesProps) => { - const narrowDateRange = useCallback( - (score: Anomaly, interval: string) => { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }, - [setAbsoluteRangeDatePicker] - ); +export const NetworkRoutes = React.memo( + ({ + networkPagePath, + type, + to, + filterQuery, + isInitializing, + from, + indexPattern, + setQuery, + setAbsoluteRangeDatePicker, + }) => { + const narrowDateRange = useCallback( + (score: Anomaly, interval: string) => { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }, + [setAbsoluteRangeDatePicker] + ); + const updateDateRange = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); - const networkAnomaliesFilterQuery = { - bool: { - should: [ - { - exists: { - field: 'source.ip', + const networkAnomaliesFilterQuery = { + bool: { + should: [ + { + exists: { + field: 'source.ip', + }, }, - }, - { - exists: { - field: 'destination.ip', + { + exists: { + field: 'destination.ip', + }, }, - }, - ], - minimum_should_match: 1, - }, - }; + ], + minimum_should_match: 1, + }, + }; - const commonProps = { - startDate: from, - endDate: to, - skip: isInitializing, - type, - narrowDateRange, - setQuery, - filterQuery, - }; + const commonProps = { + startDate: from, + endDate: to, + skip: isInitializing, + type, + narrowDateRange, + setQuery, + filterQuery, + }; - const tabProps = { - ...commonProps, - indexPattern, - }; + const tabProps = { + ...commonProps, + indexPattern, + updateDateRange, + }; - const anomaliesProps = { - ...commonProps, - anomaliesFilterQuery: networkAnomaliesFilterQuery, - AnomaliesTableComponent: AnomaliesNetworkTable, - }; + const anomaliesProps = { + ...commonProps, + anomaliesFilterQuery: networkAnomaliesFilterQuery, + AnomaliesTableComponent: AnomaliesNetworkTable, + }; - return ( - - } - /> - ( + return ( + + + + + <> @@ -118,31 +123,25 @@ export const NetworkRoutes = ({ - )} - /> - } - /> - } - /> - ( + + + + + + + + - )} - /> - } - /> - - ); -}; + + + + + + ); + } +); NetworkRoutes.displayName = 'NetworkRoutes'; diff --git a/x-pack/legacy/plugins/siem/public/routes.tsx b/x-pack/legacy/plugins/siem/public/routes.tsx index cbb58a473e8ea..a989fa9873435 100644 --- a/x-pack/legacy/plugins/siem/public/routes.tsx +++ b/x-pack/legacy/plugins/siem/public/routes.tsx @@ -20,8 +20,12 @@ const PageRouterComponent: FC = ({ history }) => ( - } /> - } /> + + + + + + diff --git a/x-pack/legacy/plugins/siem/public/utils/kql/use_update_kql.tsx b/x-pack/legacy/plugins/siem/public/utils/kql/use_update_kql.tsx index 213b881bd2084..af993588f7e0d 100644 --- a/x-pack/legacy/plugins/siem/public/utils/kql/use_update_kql.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/kql/use_update_kql.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; import { Dispatch } from 'redux'; import { IIndexPattern } from 'src/plugins/data/public'; +import deepEqual from 'fast-deep-equal'; import { KueryFilterQuery } from '../../store'; import { applyKqlFilterQuery as dispatchApplyTimelineFilterQuery } from '../../store/timeline/actions'; @@ -29,7 +29,7 @@ export const useUpdateKql = ({ timelineId, }: UseUpdateKqlProps): RefetchKql => { const updateKql: RefetchKql = (dispatch: Dispatch) => { - if (kueryFilterQueryDraft != null && !isEqual(kueryFilterQuery, kueryFilterQueryDraft)) { + if (kueryFilterQueryDraft != null && !deepEqual(kueryFilterQuery, kueryFilterQueryDraft)) { if (storeType === 'timelineType' && timelineId != null) { dispatch( dispatchApplyTimelineFilterQuery({ diff --git a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx index c88562abef6ae..ddee2359b28ba 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx @@ -5,7 +5,6 @@ */ import * as H from 'history'; -import { isEqual } from 'lodash/fp'; import { memo, useEffect, useState } from 'react'; import { withRouter } from 'react-router-dom'; import deepEqual from 'fast-deep-equal'; @@ -35,7 +34,7 @@ export const SpyRouteComponent = memo( } }, [search]); useEffect(() => { - if (pageName && !isEqual(route.pathName, pathname)) { + if (pageName && !deepEqual(route.pathName, pathname)) { if (isInitializing && detailName == null) { dispatch({ type: 'updateRouteWithOutSearch', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 1578c71dddc6a..2b50011cf4dff 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -427,13 +427,54 @@ export const getMockPrivileges = () => ({ has_encryption_key: true, }); -export const getFindResultStatus = (): SavedObjectsFindResponse => ({ +export const getFindResultStatusEmpty = (): SavedObjectsFindResponse => ({ page: 1, per_page: 1, total: 0, saved_objects: [], }); +export const getFindResultStatus = (): SavedObjectsFindResponse => ({ + page: 1, + per_page: 6, + total: 2, + saved_objects: [ + { + type: 'my-type', + id: 'e0b86950-4e9f-11ea-bdbd-07b56aa159b3', + attributes: { + alertId: '1ea5a820-4da1-4e82-92a1-2b43a7bece08', + statusDate: '2020-02-18T15:26:49.783Z', + status: 'succeeded', + lastFailureAt: null, + lastSuccessAt: '2020-02-18T15:26:49.783Z', + lastFailureMessage: null, + lastSuccessMessage: 'succeeded', + }, + references: [], + updated_at: '2020-02-18T15:26:51.333Z', + version: 'WzQ2LDFd', + }, + { + type: 'my-type', + id: '91246bd0-5261-11ea-9650-33b954270f67', + attributes: { + alertId: '1ea5a820-4da1-4e82-92a1-2b43a7bece08', + statusDate: '2020-02-18T15:15:58.806Z', + status: 'failed', + lastFailureAt: '2020-02-18T15:15:58.806Z', + lastSuccessAt: '2020-02-13T20:31:59.855Z', + lastFailureMessage: + 'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.', + lastSuccessMessage: 'succeeded', + }, + references: [], + updated_at: '2020-02-18T15:15:58.860Z', + version: 'WzMyLDFd', + }, + ], +}); + export const getIndexName = () => 'index-name'; export const getEmptyIndex = (): { _shards: Partial } => ({ _shards: { total: 0 }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts index f8c8e1f231ffa..32226e38a1f7f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -26,6 +26,20 @@ export const getSimpleRule = (ruleId = 'rule-1'): Partial = query: 'user.name: root or user.name: admin', }); +/** + * This is a typical simple rule for testing that is easy for most basic testing + * @param ruleId + */ +export const getSimpleRuleWithId = (id = 'rule-1'): Partial => ({ + name: 'Simple Rule Query', + description: 'Simple Rule Query', + risk_score: 1, + id, + severity: 'high', + type: 'query', + query: 'user.name: root or user.name: admin', +}); + /** * Given an array of rule_id strings this will return a ndjson buffer which is useful * for testing uploads. @@ -51,3 +65,26 @@ export const getSimpleRuleAsMultipartContent = (ruleIds: string[], isNdjson = tr return Buffer.from(resultingPayload); }; + +/** + * Given an array of rule_id strings this will return a ndjson buffer which is useful + * for testing uploads. + * @param count Number of rules to generate + * @param isNdjson Boolean to determine file extension + */ +export const getSimpleRuleAsMultipartContentNoRuleId = (count: number, isNdjson = true): Buffer => { + const arrayOfRules = Array(count).fill(JSON.stringify(getSimpleRuleWithId())); + const stringOfRules = arrayOfRules.join('\r\n'); + + const resultingPayload = + `--${TEST_BOUNDARY}\r\n` + + `Content-Disposition: form-data; name="file"; filename="rules.${ + isNdjson ? 'ndjson' : 'json' + }\r\n` + + 'Content-Type: application/octet-stream\r\n' + + '\r\n' + + `${stringOfRules}\r\n` + + `--${TEST_BOUNDARY}--\r\n`; + + return Buffer.from(resultingPayload); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts index 41be42f7c0fe1..26a6c790ceef9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts @@ -36,25 +36,14 @@ export const createReadIndexRoute = ( const indexExists = await getIndexExists(callCluster, index); if (indexExists) { - // head request is used for if you want to get if the index exists - // or not and it will return a content-length: 0 along with either a 200 or 404 - // depending on if the index exists or not. - if (request.method.toLowerCase() === 'head') { - return headers.response().code(200); - } else { - return headers.response({ name: index }).code(200); - } + return headers.response({ name: index }).code(200); } else { - if (request.method.toLowerCase() === 'head') { - return headers.response().code(404); - } else { - return headers - .response({ - message: 'index for this space does not exist', - status_code: 404, - }) - .code(404); - } + return headers + .response({ + message: 'index for this space does not exist', + status_code: 404, + }) + .code(404); } } catch (err) { const error = transformError(err); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts index 308ee95a77e20..3c31658c61d6e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts @@ -5,6 +5,7 @@ */ import { readPrivilegesRoute } from './read_privileges_route'; +import * as readPrivileges from '../../privileges/read_privileges'; import { createMockServer, createMockConfig, clientsServiceMock } from '../__mocks__'; import { getPrivilegeRequest, getMockPrivileges } from '../__mocks__/request_responses'; @@ -38,5 +39,16 @@ describe('read_privileges', () => { const { payload } = await inject(getPrivilegeRequest()); expect(JSON.parse(payload)).toEqual(getMockPrivileges()); }); + + test('returns 500 when bad response from readPrivileges', async () => { + jest.spyOn(readPrivileges, 'readPrivileges').mockImplementation(() => { + throw new Error('Test error'); + }); + const { payload } = await inject(getPrivilegeRequest()); + expect(JSON.parse(payload)).toEqual({ + message: 'Test error', + status_code: 500, + }); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index e018ed4cc22ff..e6a93fdadcfca 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -6,7 +6,6 @@ import { omit } from 'lodash/fp'; -import { createRulesRoute } from './create_rules_route'; import { getFindResult, getResult, @@ -17,6 +16,7 @@ import { getNonEmptyIndex, } from '../__mocks__/request_responses'; import { createMockServer, createMockConfig, clientsServiceMock } from '../__mocks__'; +import * as updatePrepackagedRules from '../../rules/update_prepacked_rules'; jest.mock('../../rules/get_prepackaged_rules', () => { return { @@ -54,7 +54,8 @@ describe('add_prepackaged_rules_route', () => { beforeEach(() => { jest.resetAllMocks(); - + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); config = createMockConfig(); getClients = clientsServiceMock.createGetScoped(); @@ -78,9 +79,7 @@ describe('add_prepackaged_rules_route', () => { test('returns 404 if alertClient is not available on the route', async () => { getClients.mockResolvedValue(omit('alertsClient', clients)); - const { inject, route } = createMockServer(); - createRulesRoute(route, config, getClients); - const { statusCode } = await inject(addPrepackagedRulesRequest()); + const { statusCode } = await server.inject(addPrepackagedRulesRequest()); expect(statusCode).toBe(404); }); }); @@ -126,5 +125,19 @@ describe('add_prepackaged_rules_route', () => { rules_updated: 1, }); }); + test('catches errors if payloads cause errors to be thrown', async () => { + jest.spyOn(updatePrepackagedRules, 'updatePrepackagedRules').mockImplementation(() => { + throw new Error('Test error'); + }); + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.actionsClient.create.mockResolvedValue(createActionResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + const { payload } = await server.inject(addPrepackagedRulesRequest()); + expect(JSON.parse(payload)).toEqual({ + message: 'Test error', + status_code: 500, + }); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 664d27a7572ad..931623ea6652c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -14,12 +14,15 @@ import { typicalPayload, getReadBulkRequest, getEmptyIndex, + getNonEmptyIndex, } from '../__mocks__/request_responses'; import { createMockServer, createMockConfig, clientsServiceMock } from '../__mocks__'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { createRulesBulkRoute } from './create_rules_bulk_route'; import { BulkError } from '../utils'; import { OutputRuleAlertRest } from '../../types'; +import * as createRules from '../../rules/create_rules'; +import * as readRules from '../../rules/read_rules'; describe('create_rules_bulk', () => { let server = createMockServer(); @@ -29,10 +32,14 @@ describe('create_rules_bulk', () => { beforeEach(() => { jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); config = createMockConfig(); getClients = clientsServiceMock.createGetScoped(); clients = clientsServiceMock.createClients(); + clients.clusterClient.callAsCurrentUser.mockResolvedValue(getNonEmptyIndex()); + getClients.mockResolvedValue(clients); createRulesBulkRoute(server.route, config, getClients); @@ -44,8 +51,12 @@ describe('create_rules_bulk', () => { clients.alertsClient.get.mockResolvedValue(getResult()); clients.actionsClient.create.mockResolvedValue(createActionResult()); clients.alertsClient.create.mockResolvedValue(getResult()); - const { statusCode } = await server.inject(getReadBulkRequest()); + jest.spyOn(createRules, 'createRules').mockImplementation(async () => { + return getResult(); + }); + const { payload, statusCode } = await server.inject(getReadBulkRequest()); expect(statusCode).toBe(200); + expect(JSON.parse(payload).error).toBeUndefined(); }); test('returns 404 if alertClient is not available on the route', async () => { @@ -149,6 +160,24 @@ describe('create_rules_bulk', () => { expect(output.some(item => item.error?.status_code === 409)).toBeTruthy(); }); + test('returns 409 if duplicate rule_ids found in rule saved objects', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResult()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.actionsClient.create.mockResolvedValue(createActionResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + jest.spyOn(readRules, 'readRules').mockImplementation(async () => { + return getResult(); + }); + const request: ServerInjectOptions = { + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + payload: [typicalPayload()], + }; + const { payload } = await server.inject(request); + const output: Array> = JSON.parse(payload); + expect(output.some(item => item.error?.status_code === 409)).toBeTruthy(); + }); + test('returns one error object in response when duplicate rule_ids found in request payload', async () => { clients.alertsClient.find.mockResolvedValue(getFindResult()); clients.alertsClient.get.mockResolvedValue(getResult()); @@ -163,4 +192,22 @@ describe('create_rules_bulk', () => { const output: Array> = JSON.parse(payload); expect(output.length).toBe(1); }); + + test('catches error if createRules throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResult()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.actionsClient.create.mockResolvedValue(createActionResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + jest.spyOn(createRules, 'createRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const request: ServerInjectOptions = { + method: 'POST', + url: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + payload: [typicalPayload()], + }; + const { payload } = await server.inject(request); + const output: Array> = JSON.parse(payload); + expect(output[0].error.message).toBe('Test error'); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 51b7b132fc794..08a0589389966 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -5,7 +5,6 @@ */ import Hapi from 'hapi'; -import { countBy } from 'lodash/fp'; import uuid from 'uuid'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; @@ -45,8 +44,7 @@ export const createCreateRulesBulkRoute = ( } const ruleDefinitions = request.payload; - const mappedDuplicates = countBy('rule_id', ruleDefinitions); - const dupes = getDuplicates(mappedDuplicates); + const dupes = getDuplicates(ruleDefinitions, 'rule_id'); const rules = await Promise.all( ruleDefinitions diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 4f28771db8ed7..5ad43e70f2651 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -9,6 +9,9 @@ import { omit } from 'lodash/fp'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { createRulesRoute } from './create_rules_route'; +import * as createRules from '../../rules/create_rules'; +import * as readRules from '../../rules/read_rules'; +import * as utils from './utils'; import { getFindResult, @@ -29,8 +32,12 @@ describe('create_rules', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); - + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); config = createMockConfig(); getClients = clientsServiceMock.createGetScoped(); @@ -130,5 +137,44 @@ describe('create_rules', () => { const { statusCode } = await server.inject(request); expect(statusCode).toBe(400); }); + + test('catches error if createRules throws error', async () => { + clients.actionsClient.create.mockResolvedValue(createActionResult()); + clients.alertsClient.find.mockResolvedValue(getFindResult()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(createRules, 'createRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getCreateRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); + + test('catches error if transform returns null', async () => { + clients.actionsClient.create.mockResolvedValue(createActionResult()); + clients.alertsClient.find.mockResolvedValue(getFindResult()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(utils, 'transform').mockReturnValue(null); + const { payload, statusCode } = await server.inject(getCreateRequest()); + expect(JSON.parse(payload).message).toBe('Internal error transforming rules'); + expect(statusCode).toBe(500); + }); + + test('returns 409 if duplicate rule_ids found in rule saved objects', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResult()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.actionsClient.create.mockResolvedValue(createActionResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + jest.spyOn(readRules, 'readRules').mockImplementation(async () => { + return getResult(); + }); + const { payload } = await server.inject(getCreateRequest()); + const output = JSON.parse(payload); + expect(output.status_code).toEqual(409); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts index 855bf7f634c26..fb44f96d76859 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -48,6 +48,16 @@ describe('delete_rules', () => { expect(statusCode).toBe(200); }); + test('resturns 200 when deleting a single rule and related rule status', async () => { + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + clients.savedObjectsClient.delete.mockResolvedValue(true); + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.alertsClient.delete.mockResolvedValue({}); + const { statusCode } = await server.inject(getDeleteBulkRequest()); + expect(statusCode).toBe(200); + }); + test('returns 200 when deleting a single rule with a valid actionClient and alertClient by alertId using POST', async () => { clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); clients.alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 6438318cb43db..aabf3e513bfea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -58,7 +58,7 @@ export const createDeleteRulesBulkRoute = (getClients: GetScopedClients): Hapi.S ruleStatuses.saved_objects.forEach(async obj => savedObjectsClient.delete(ruleStatusSavedObjectType, obj.id) ); - return transformOrBulkError(idOrRuleIdOrUnknown, rule); + return transformOrBulkError(idOrRuleIdOrUnknown, rule, ruleStatuses); } else { return getIdBulkError({ id, ruleId }); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index a0a6f61223279..57c7c85976619 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -7,6 +7,8 @@ import { ServerInjectOptions } from 'hapi'; import { omit } from 'lodash/fp'; import { deleteRulesRoute } from './delete_rules_route'; +import * as utils from './utils'; +import * as deleteRules from '../../rules/delete_rules'; import { getFindResult, @@ -25,7 +27,12 @@ describe('delete_rules', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); getClients = clientsServiceMock.createGetScoped(); clients = clientsServiceMock.createClients(); @@ -73,6 +80,32 @@ describe('delete_rules', () => { const { statusCode } = await inject(getDeleteRequest()); expect(statusCode).toBe(404); }); + + test('returns 500 when transform fails', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.alertsClient.delete.mockResolvedValue({}); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + clients.savedObjectsClient.delete.mockResolvedValue({}); + jest.spyOn(utils, 'transform').mockReturnValue(null); + const { payload, statusCode } = await server.inject(getDeleteRequest()); + expect(JSON.parse(payload).message).toBe('Internal error transforming rules'); + expect(statusCode).toBe(500); + }); + + test('catches error if deleteRules throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.alertsClient.delete.mockResolvedValue({}); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + clients.savedObjectsClient.delete.mockResolvedValue({}); + jest.spyOn(deleteRules, 'deleteRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getDeleteRequestById()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index 5b75f17164acf..019424ea2420a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -6,13 +6,22 @@ import { omit } from 'lodash/fp'; +import { + getFindResult, + getResult, + getFindResultWithSingleHit, + getFindResultStatus, + getFindRequest, +} from '../__mocks__/request_responses'; import { createMockServer } from '../__mocks__'; import { clientsServiceMock } from '../__mocks__/clients_service_mock'; +import * as utils from './utils'; +import * as findRules from '../../rules/find_rules'; + import { findRulesRoute } from './find_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { getFindResult, getResult, getFindRequest } from '../__mocks__/request_responses'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('find_rules', () => { @@ -21,7 +30,12 @@ describe('find_rules', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); getClients = clientsServiceMock.createGetScoped(); @@ -33,15 +47,10 @@ describe('find_rules', () => { }); describe('status codes with actionClient and alertClient', () => { - test('returns 200 when finding a single rule with a valid actionClient and alertClient', async () => { - clients.alertsClient.find.mockResolvedValue(getFindResult()); - clients.actionsClient.find.mockResolvedValue({ - page: 1, - perPage: 1, - total: 0, - data: [], - }); + test('returns 200 when finding a single rule with a valid alertsClient', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); const { statusCode } = await server.inject(getFindRequest()); expect(statusCode).toBe(200); }); @@ -53,6 +62,28 @@ describe('find_rules', () => { const { statusCode } = await inject(getFindRequest()); expect(statusCode).toBe(404); }); + + test('catches error when transformation fails', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(utils, 'transformFindAlerts').mockReturnValue(null); + const { payload, statusCode } = await server.inject(getFindRequest()); + expect(statusCode).toBe(500); + expect(JSON.parse(payload).message).toBe('unknown data type, error transforming alert'); + }); + + test('catch error when findRules function throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(findRules, 'findRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getFindRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts new file mode 100644 index 0000000000000..00411c550fa2e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { omit } from 'lodash/fp'; + +import { getFindResultStatus } from '../__mocks__/request_responses'; +import { createMockServer } from '../__mocks__'; +import { clientsServiceMock } from '../__mocks__/clients_service_mock'; + +import { findRulesStatusesRoute } from './find_rules_status_route'; +import { ServerInjectOptions } from 'hapi'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; + +describe('find_statuses', () => { + let server = createMockServer(); + let getClients = clientsServiceMock.createGetScoped(); + let clients = clientsServiceMock.createClients(); + + beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 + jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); + + server = createMockServer(); + getClients = clientsServiceMock.createGetScoped(); + clients = clientsServiceMock.createClients(); + + getClients.mockResolvedValue(clients); + + findRulesStatusesRoute(server.route, getClients); + }); + + describe('status codes with actionClient and alertClient', () => { + test('returns 200 when finding a single rule status with a valid alertsClient', async () => { + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + const request: ServerInjectOptions = { + method: 'GET', + url: `${DETECTION_ENGINE_RULES_URL}/_find_statuses?ids=["someid"]`, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 404 if alertClient is not available on the route', async () => { + getClients.mockResolvedValue(omit('alertsClient', clients)); + const request: ServerInjectOptions = { + method: 'GET', + url: `${DETECTION_ENGINE_RULES_URL}/_find_statuses?ids=["someid"]`, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(404); + }); + + test('catch error when savedObjectsClient find function throws error', async () => { + clients.savedObjectsClient.find.mockImplementation(async () => { + throw new Error('Test error'); + }); + const request: ServerInjectOptions = { + method: 'GET', + url: `${DETECTION_ENGINE_RULES_URL}/_find_statuses?ids=["someid"]`, + }; + const { payload, statusCode } = await server.inject(request); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); + }); + + describe('validation', () => { + test('returns 400 if id is given instead of ids', async () => { + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + const request: ServerInjectOptions = { + method: 'GET', + url: `${DETECTION_ENGINE_RULES_URL}/_find_statuses?id=["someid"]`, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + + test('returns 200 if the set of optional query parameters are given', async () => { + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + const request: ServerInjectOptions = { + method: 'GET', + url: `${DETECTION_ENGINE_RULES_URL}/_find_statuses?ids=["someid"]`, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + }); +}); 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 fe8742ff0b60c..c496c7b7ce59c 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 @@ -5,7 +5,6 @@ */ import Hapi from 'hapi'; -import { snakeCase } from 'lodash/fp'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { LegacyServices, LegacyRequest } from '../../../../types'; @@ -18,17 +17,7 @@ import { IRuleStatusAttributes, } from '../../rules/types'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const convertToSnakeCase = >(obj: T): Partial | null => { - if (!obj) { - return null; - } - return Object.keys(obj).reduce((acc, item) => { - const newKey = snakeCase(item); - return { ...acc, [newKey]: obj[item] }; - }, {}); -}; +import { transformError, convertToSnakeCase } from '../utils'; export const createFindRulesStatusRoute = (getClients: GetScopedClients): Hapi.ServerRoute => ({ method: 'GET', @@ -57,33 +46,43 @@ export const createFindRulesStatusRoute = (getClients: GetScopedClients): Hapi.S "anotherAlertId": ... } */ - const statuses = await query.ids.reduce>(async (acc, id) => { - const lastFiveErrorsForId = await savedObjectsClient.find< - IRuleSavedAttributesSavedObjectAttributes - >({ - type: ruleStatusSavedObjectType, - perPage: 6, - sortField: 'statusDate', - sortOrder: 'desc', - search: id, - searchFields: ['alertId'], - }); - const accumulated = await acc; - const currentStatus = convertToSnakeCase( - lastFiveErrorsForId.saved_objects[0]?.attributes - ); - const failures = lastFiveErrorsForId.saved_objects - .slice(1) - .map(errorItem => convertToSnakeCase(errorItem.attributes)); - return { - ...accumulated, - [id]: { - current_status: currentStatus, - failures, - }, - }; - }, Promise.resolve({})); - return statuses; + try { + const statuses = await query.ids.reduce>(async (acc, id) => { + const lastFiveErrorsForId = await savedObjectsClient.find< + IRuleSavedAttributesSavedObjectAttributes + >({ + type: ruleStatusSavedObjectType, + perPage: 6, + sortField: 'statusDate', + sortOrder: 'desc', + search: id, + searchFields: ['alertId'], + }); + const accumulated = await acc; + const currentStatus = convertToSnakeCase( + lastFiveErrorsForId.saved_objects[0]?.attributes + ); + const failures = lastFiveErrorsForId.saved_objects + .slice(1) + .map(errorItem => convertToSnakeCase(errorItem.attributes)); + return { + ...accumulated, + [id]: { + current_status: currentStatus, + failures, + }, + }; + }, Promise.resolve({})); + return statuses; + } catch (err) { + const error = transformError(err); + return headers + .response({ + message: error.message, + status_code: error.statusCode, + }) + .code(error.statusCode); + } }, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts index 8f27910a7e5e2..99157b4d15360 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts @@ -7,6 +7,7 @@ import { omit } from 'lodash/fp'; import { getPrepackagedRulesStatusRoute } from './get_prepackaged_rules_status_route'; +import * as findRules from '../../rules/find_rules'; import { getFindResult, @@ -47,7 +48,12 @@ describe('get_prepackaged_rule_status_route', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); getClients = clientsServiceMock.createGetScoped(); @@ -76,6 +82,17 @@ describe('get_prepackaged_rule_status_route', () => { const { statusCode } = await inject(getPrepackagedRulesStatusRequest()); expect(statusCode).toBe(404); }); + + test('catch error when findRules function throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + jest.spyOn(findRules, 'findRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getPrepackagedRulesStatusRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('payload', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts index b1dd08f8ca371..c8b77e505b5d7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.test.ts @@ -8,6 +8,7 @@ import { omit } from 'lodash/fp'; import { getSimpleRuleAsMultipartContent, + getSimpleRuleAsMultipartContentNoRuleId, TEST_BOUNDARY, UNPARSABLE_LINE, getSimpleRule, @@ -25,6 +26,7 @@ import { import { createMockServer, createMockConfig, clientsServiceMock } from '../__mocks__'; import { importRulesRoute } from './import_rules_route'; import { DEFAULT_SIGNALS_INDEX } from '../../../../../common/constants'; +import * as createRulesStreamFromNdJson from '../../rules/create_rules_stream_from_ndjson'; describe('import_rules_route', () => { let server = createMockServer(); @@ -33,8 +35,12 @@ describe('import_rules_route', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); - + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); config = createMockConfig(); config = () => ({ @@ -94,6 +100,21 @@ describe('import_rules_route', () => { const { statusCode } = await inject(getImportRulesRequest(requestPayload)); expect(statusCode).toEqual(404); }); + + test('returns error if createPromiseFromStreams throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResult()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.alertsClient.create.mockResolvedValue(getResult()); + jest + .spyOn(createRulesStreamFromNdJson, 'createRulesStreamFromNdJson') + .mockImplementation(() => { + throw new Error('Test error'); + }); + const requestPayload = getSimpleRuleAsMultipartContent(['rule-1']); + const { payload, statusCode } = await server.inject(getImportRulesRequest(requestPayload)); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { @@ -306,6 +327,21 @@ describe('import_rules_route', () => { expect(statusCode).toEqual(200); }); + test('returns 200 with errors if all rules are missing rule_ids and import fails on validation', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResult()); + + const requestPayload = getSimpleRuleAsMultipartContentNoRuleId(2); + const { statusCode, payload } = await server.inject(getImportRulesRequest(requestPayload)); + const parsed: ImportSuccessError = JSON.parse(payload); + + expect(parsed.success).toEqual(false); + expect(parsed.errors[0].error.message).toEqual( + 'child "rule_id" fails because ["rule_id" is required]' + ); + expect(parsed.errors[0].error.status_code).toEqual(400); + expect(statusCode).toEqual(200); + }); + test('returns 200 with reported conflict if error parsing rule', async () => { const multipartPayload = `--${TEST_BOUNDARY}\r\n` + diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index f438e0120f96a..a9358a47f25fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -7,6 +7,7 @@ import Hapi from 'hapi'; import { chunk, isEmpty } from 'lodash/fp'; import { extname } from 'path'; +import { Readable } from 'stream'; import { createPromiseFromStreams } from '../../../../../../../../../src/legacy/utils/streams'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; @@ -15,7 +16,7 @@ import { createRules } from '../../rules/create_rules'; import { ImportRulesRequest } from '../../rules/types'; import { readRules } from '../../rules/read_rules'; import { getIndexExists } from '../../index/get_index_exists'; -import { getIndex, createBulkErrorObject, ImportRuleResponse } from '../utils'; +import { getIndex, transformError, createBulkErrorObject, ImportRuleResponse } from '../utils'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { ImportRuleAlertRest } from '../../types'; import { patchRules } from '../../rules/patch_rules'; @@ -74,179 +75,192 @@ export const createImportRulesRoute = ( } const objectLimit = config().get('savedObjects.maxImportExportSize'); - const readStream = createRulesStreamFromNdJson(request.payload.file, objectLimit); - const parsedObjects = await createPromiseFromStreams([readStream]); - const [duplicateIdErrors, uniqueParsedObjects] = getTupleDuplicateErrorsAndUniqueRules( - parsedObjects, - request.query.overwrite - ); + try { + const readStream = createRulesStreamFromNdJson(objectLimit); + const parsedObjects = await createPromiseFromStreams([ + request.payload.file as Readable, + ...readStream, + ]); + const [duplicateIdErrors, uniqueParsedObjects] = getTupleDuplicateErrorsAndUniqueRules( + parsedObjects, + request.query.overwrite + ); - const chunkParseObjects = chunk(CHUNK_PARSED_OBJECT_SIZE, uniqueParsedObjects); - let importRuleResponse: ImportRuleResponse[] = []; + const chunkParseObjects = chunk(CHUNK_PARSED_OBJECT_SIZE, uniqueParsedObjects); + let importRuleResponse: ImportRuleResponse[] = []; - while (chunkParseObjects.length) { - const batchParseObjects = chunkParseObjects.shift() ?? []; - const newImportRuleResponse = await Promise.all( - batchParseObjects.reduce>>((accum, parsedRule) => { - const importsWorkerPromise = new Promise( - async (resolve, reject) => { - if (parsedRule instanceof Error) { - // If the JSON object had a validation or parse error then we return - // early with the error and an (unknown) for the ruleId - resolve( - createBulkErrorObject({ - statusCode: 400, - message: parsedRule.message, - }) - ); - return null; - } - const { - description, - enabled, - false_positives: falsePositives, - from, - immutable, - query, - language, - output_index: outputIndex, - saved_id: savedId, - meta, - filters, - rule_id: ruleId, - index, - interval, - max_signals: maxSignals, - risk_score: riskScore, - name, - severity, - tags, - threat, - to, - type, - references, - timeline_id: timelineId, - timeline_title: timelineTitle, - version, - } = parsedRule; - try { - const finalIndex = getIndex(spacesClient.getSpaceId, config); - const indexExists = await getIndexExists( - clusterClient.callAsCurrentUser, - finalIndex - ); - if (!indexExists) { + while (chunkParseObjects.length) { + const batchParseObjects = chunkParseObjects.shift() ?? []; + const newImportRuleResponse = await Promise.all( + batchParseObjects.reduce>>((accum, parsedRule) => { + const importsWorkerPromise = new Promise( + async (resolve, reject) => { + if (parsedRule instanceof Error) { + // If the JSON object had a validation or parse error then we return + // early with the error and an (unknown) for the ruleId resolve( createBulkErrorObject({ - ruleId, - statusCode: 409, - message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, + statusCode: 400, + message: parsedRule.message, }) ); + return null; } - const rule = await readRules({ alertsClient, ruleId }); - if (rule == null) { - await createRules({ - alertsClient, - actionsClient, - description, - enabled, - falsePositives, - from, - immutable, - query, - language, - outputIndex: finalIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - ruleId, - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threat, - references, - version, - }); - resolve({ rule_id: ruleId, status_code: 200 }); - } else if (rule != null && request.query.overwrite) { - await patchRules({ - alertsClient, - actionsClient, - savedObjectsClient, - description, - enabled, - falsePositives, - from, - immutable, - query, - language, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - id: undefined, - ruleId, - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threat, - references, - version, - }); - resolve({ rule_id: ruleId, status_code: 200 }); - } else if (rule != null) { + const { + description, + enabled, + false_positives: falsePositives, + from, + immutable, + query, + language, + output_index: outputIndex, + saved_id: savedId, + meta, + filters, + rule_id: ruleId, + index, + interval, + max_signals: maxSignals, + risk_score: riskScore, + name, + severity, + tags, + threat, + to, + type, + references, + timeline_id: timelineId, + timeline_title: timelineTitle, + version, + } = parsedRule; + try { + const finalIndex = getIndex(spacesClient.getSpaceId, config); + const indexExists = await getIndexExists( + clusterClient.callAsCurrentUser, + finalIndex + ); + if (!indexExists) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: 409, + message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, + }) + ); + } + const rule = await readRules({ alertsClient, ruleId }); + if (rule == null) { + await createRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex: finalIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threat, + references, + version, + }); + resolve({ rule_id: ruleId, status_code: 200 }); + } else if (rule != null && request.query.overwrite) { + await patchRules({ + alertsClient, + actionsClient, + savedObjectsClient, + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + id: undefined, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threat, + references, + version, + }); + resolve({ rule_id: ruleId, status_code: 200 }); + } else if (rule != null) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: 409, + message: `rule_id: "${ruleId}" already exists`, + }) + ); + } + } catch (err) { resolve( createBulkErrorObject({ ruleId, - statusCode: 409, - message: `rule_id: "${ruleId}" already exists`, + statusCode: 400, + message: err.message, }) ); } - } catch (err) { - resolve( - createBulkErrorObject({ - ruleId, - statusCode: 400, - message: err.message, - }) - ); } - } - ); - return [...accum, importsWorkerPromise]; - }, []) - ); - importRuleResponse = [ - ...duplicateIdErrors, - ...importRuleResponse, - ...newImportRuleResponse, - ]; - } + ); + return [...accum, importsWorkerPromise]; + }, []) + ); + importRuleResponse = [ + ...duplicateIdErrors, + ...importRuleResponse, + ...newImportRuleResponse, + ]; + } - const errorsResp = importRuleResponse.filter(resp => !isEmpty(resp.error)); - return { - success: errorsResp.length === 0, - success_count: importRuleResponse.filter(resp => resp.status_code === 200).length, - errors: errorsResp, - }; + const errorsResp = importRuleResponse.filter(resp => !isEmpty(resp.error)); + return { + success: errorsResp.length === 0, + success_count: importRuleResponse.filter(resp => resp.status_code === 200).length, + errors: errorsResp, + }; + } catch (exc) { + const error = transformError(exc); + return headers + .response({ + message: error.message, + status_code: error.statusCode, + }) + .code(error.statusCode); + } }, }; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk.test.ts index 02af4135b534f..1cfb4ae81ab85 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk.test.ts @@ -15,6 +15,7 @@ import { typicalPayload, getFindResultWithSingleHit, getPatchBulkRequest, + getFindResultStatus, } from '../__mocks__/request_responses'; import { createMockServer, clientsServiceMock } from '../__mocks__'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; @@ -43,6 +44,7 @@ describe('patch_rules_bulk', () => { clients.alertsClient.get.mockResolvedValue(getResult()); clients.actionsClient.update.mockResolvedValue(updateActionResult()); clients.alertsClient.update.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); const { statusCode } = await server.inject(getPatchBulkRequest()); expect(statusCode).toBe(200); }); @@ -56,6 +58,13 @@ describe('patch_rules_bulk', () => { expect(statusCode).toBe(200); }); + test('returns 404 as a response when missing alertsClient', async () => { + getClients.mockResolvedValue(omit('alertsClient', clients)); + clients.actionsClient.update.mockResolvedValue(updateActionResult()); + const { statusCode } = await server.inject(getPatchBulkRequest()); + expect(statusCode).toBe(404); + }); + test('returns 404 within the payload when updating a single rule that does not exist', async () => { clients.alertsClient.find.mockResolvedValue(getFindResult()); clients.alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index cc84b08fdef11..04cd3a026562f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -7,6 +7,8 @@ import { ServerInjectOptions } from 'hapi'; import { omit } from 'lodash/fp'; import { patchRulesRoute } from './patch_rules_route'; +import * as utils from './utils'; +import * as patchRules from '../../rules/patch_rules'; import { getFindResult, @@ -26,7 +28,13 @@ describe('patch_rules', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); + server = createMockServer(); getClients = clientsServiceMock.createGetScoped(); clients = clientsServiceMock.createClients(); @@ -63,6 +71,32 @@ describe('patch_rules', () => { const { statusCode } = await inject(getPatchRequest()); expect(statusCode).toBe(404); }); + + test('returns 500 when transform fails', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.actionsClient.update.mockResolvedValue(updateActionResult()); + clients.alertsClient.update.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(utils, 'transform').mockReturnValue(null); + const { payload, statusCode } = await server.inject(getPatchRequest()); + expect(JSON.parse(payload).message).toBe('Internal error transforming rules'); + expect(statusCode).toBe(500); + }); + + test('catches error if patchRules throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.actionsClient.update.mockResolvedValue(updateActionResult()); + clients.alertsClient.update.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(patchRules, 'patchRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getPatchRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index 7c4653af97f21..0366d3648e1ea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -7,6 +7,9 @@ import { ServerInjectOptions } from 'hapi'; import { omit } from 'lodash/fp'; +import * as utils from './utils'; +import * as readRules from '../../rules/read_rules'; + import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { readRulesRoute } from './read_rules_route'; import { @@ -24,8 +27,12 @@ describe('read_signals', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); - + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); getClients = clientsServiceMock.createGetScoped(); clients = clientsServiceMock.createClients(); @@ -50,6 +57,38 @@ describe('read_signals', () => { const { statusCode } = await inject(getReadRequest()); expect(statusCode).toBe(404); }); + + test('returns error if readRules returns null', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(readRules, 'readRules').mockResolvedValue(null); + const { payload, statusCode } = await server.inject(getReadRequest()); + expect(JSON.parse(payload).message).toBe('rule_id: "rule-1" not found'); + expect(statusCode).toBe(404); + }); + + test('returns 500 when transform fails', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(utils, 'transform').mockReturnValue(null); + const { payload, statusCode } = await server.inject(getReadRequest()); + expect(JSON.parse(payload).message).toBe('Internal error transforming rules'); + expect(statusCode).toBe(500); + }); + + test('catches error if readRules throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(readRules, 'readRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getReadRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts index 9ff7ebc37aab1..32a633799ad44 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts @@ -75,9 +75,9 @@ describe('update_rules_bulk', () => { test('returns 404 if alertClient is not available on the route', async () => { getClients.mockResolvedValue(omit('alertsClient', clients)); - const { route, inject } = createMockServer(); + const { route } = createMockServer(); updateRulesRoute(route, config, getClients); - const { statusCode } = await inject(getUpdateBulkRequest()); + const { statusCode } = await server.inject(getUpdateBulkRequest()); expect(statusCode).toBe(404); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index 7cadfa94467a7..c3a92ed9a61ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -7,6 +7,9 @@ import { ServerInjectOptions } from 'hapi'; import { omit } from 'lodash/fp'; +import * as utils from './utils'; +import * as updateRules from '../../rules/update_rules'; + import { updateRulesRoute } from './update_rules_route'; import { getFindResult, @@ -27,7 +30,12 @@ describe('update_rules', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); server = createMockServer(); config = createMockConfig(); @@ -66,6 +74,28 @@ describe('update_rules', () => { const { statusCode } = await inject(getUpdateRequest()); expect(statusCode).toBe(404); }); + + test('returns 500 when transform fails', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(utils, 'transform').mockReturnValue(null); + const { payload, statusCode } = await server.inject(getUpdateRequest()); + expect(JSON.parse(payload).message).toBe('Internal error transforming rules'); + expect(statusCode).toBe(500); + }); + + test('catches error if readRules throws error', async () => { + clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + clients.alertsClient.get.mockResolvedValue(getResult()); + clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + jest.spyOn(updateRules, 'updateRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getUpdateRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index 593c55bcae9f2..5fac3f79f359c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -20,7 +20,7 @@ import { } from './utils'; import { getResult } from '../__mocks__/request_responses'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; -import { OutputRuleAlertRest, ImportRuleAlertRest } from '../../types'; +import { OutputRuleAlertRest, ImportRuleAlertRest, RuleAlertParamsRest } from '../../types'; import { BulkError, ImportSuccessError } from '../utils'; import { sampleRule } from '../../signals/__mocks__/es_results'; import { getSimpleRule } from '../__mocks__/utils'; @@ -1222,20 +1222,32 @@ describe('utils', () => { describe('getDuplicates', () => { test("returns array of ruleIds showing the duplicate keys of 'value2' and 'value3'", () => { - const output = getDuplicates({ - value1: 1, - value2: 2, - value3: 2, - }); + const output = getDuplicates( + [ + { rule_id: 'value1' }, + { rule_id: 'value2' }, + { rule_id: 'value2' }, + { rule_id: 'value3' }, + { rule_id: 'value3' }, + {}, + {}, + ] as RuleAlertParamsRest[], + 'rule_id' + ); const expected = ['value2', 'value3']; expect(output).toEqual(expected); }); test('returns null when given a map of no duplicates', () => { - const output = getDuplicates({ - value1: 1, - value2: 1, - value3: 1, - }); + const output = getDuplicates( + [ + { rule_id: 'value1' }, + { rule_id: 'value2' }, + { rule_id: 'value3' }, + {}, + {}, + ] as RuleAlertParamsRest[], + 'rule_id' + ); const expected: string[] = []; expect(output).toEqual(expected); }); @@ -1251,9 +1263,10 @@ describe('utils', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); const parsedObjects = await createPromiseFromStreams([ - rulesObjectsStream, + ndJsonStream, + ...rulesObjectsStream, ]); const [errors, output] = getTupleDuplicateErrorsAndUniqueRules(parsedObjects, false); const isInstanceOfError = output[0] instanceof Error; @@ -1272,9 +1285,10 @@ describe('utils', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); const parsedObjects = await createPromiseFromStreams([ - rulesObjectsStream, + ndJsonStream, + ...rulesObjectsStream, ]); const [errors, output] = getTupleDuplicateErrorsAndUniqueRules(parsedObjects, false); @@ -1290,6 +1304,30 @@ describe('utils', () => { ]); }); + test('returns tuple of duplicate conflict error and single rule when rules with matching ids passed in and `overwrite` is false', async () => { + const rule = getSimpleRule('rule-1'); + delete rule.rule_id; + const rule2 = getSimpleRule('rule-1'); + delete rule2.rule_id; + const ndJsonStream = new Readable({ + read() { + this.push(`${JSON.stringify(rule)}\n`); + this.push(`${JSON.stringify(rule2)}\n`); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const parsedObjects = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); + const [errors, output] = getTupleDuplicateErrorsAndUniqueRules(parsedObjects, false); + const isInstanceOfError = output[0] instanceof Error; + + expect(isInstanceOfError).toEqual(true); + expect(errors).toEqual([]); + }); + test('returns tuple of empty duplicate errors array and single rule when rules with matching rule-ids passed in and `overwrite` is true', async () => { const rule = getSimpleRule('rule-1'); const rule2 = getSimpleRule('rule-1'); @@ -1300,9 +1338,10 @@ describe('utils', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); const parsedObjects = await createPromiseFromStreams([ - rulesObjectsStream, + ndJsonStream, + ...rulesObjectsStream, ]); const [errors, output] = getTupleDuplicateErrorsAndUniqueRules(parsedObjects, true); @@ -1320,9 +1359,10 @@ describe('utils', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); const parsedObjects = await createPromiseFromStreams([ - rulesObjectsStream, + ndJsonStream, + ...rulesObjectsStream, ]); const [errors, output] = getTupleDuplicateErrorsAndUniqueRules(parsedObjects, false); const isInstanceOfError = output[0] instanceof Error; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index 198cdbfb9771d..7004bf2088ef2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pickBy } from 'lodash/fp'; -import { Dictionary } from 'lodash'; +import { pickBy, countBy } from 'lodash/fp'; import { SavedObject } from 'kibana/server'; import uuid from 'uuid'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; @@ -18,7 +17,7 @@ import { isRuleStatusFindTypes, isRuleStatusSavedObjectType, } from '../../rules/types'; -import { OutputRuleAlertRest, ImportRuleAlertRest } from '../../types'; +import { OutputRuleAlertRest, ImportRuleAlertRest, RuleAlertParamsRest } from '../../types'; import { createBulkErrorObject, BulkError, @@ -180,9 +179,7 @@ export const transform = ( if (!ruleStatus && isAlertType(alert)) { return transformAlertToRule(alert); } - if (isAlertType(alert) && isRuleStatusFindType(ruleStatus)) { - return transformAlertToRule(alert, ruleStatus.saved_objects[0]); - } else if (isAlertType(alert) && isRuleStatusSavedObjectType(ruleStatus)) { + if (isAlertType(alert) && isRuleStatusSavedObjectType(ruleStatus)) { return transformAlertToRule(alert, ruleStatus); } else { return null; @@ -195,7 +192,7 @@ export const transformOrBulkError = ( ruleStatus?: unknown ): Partial | BulkError => { if (isAlertType(alert)) { - if (isRuleStatusFindType(ruleStatus)) { + if (isRuleStatusFindType(ruleStatus) && ruleStatus?.saved_objects.length > 0) { return transformAlertToRule(alert, ruleStatus?.saved_objects[0] ?? ruleStatus); } else { return transformAlertToRule(alert); @@ -226,10 +223,14 @@ export const transformOrImportError = ( } }; -export const getDuplicates = (lodashDict: Dictionary): string[] => { - const hasDuplicates = Object.values(lodashDict).some(i => i > 1); +export const getDuplicates = (ruleDefinitions: RuleAlertParamsRest[], by: 'rule_id'): string[] => { + const mappedDuplicates = countBy( + by, + ruleDefinitions.filter(r => r[by] != null) + ); + const hasDuplicates = Object.values(mappedDuplicates).some(i => i > 1); if (hasDuplicates) { - return Object.keys(lodashDict).filter(key => lodashDict[key] > 1); + return Object.keys(mappedDuplicates).filter(key => mappedDuplicates[key] > 1); } return []; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index 3e7ed4de6d8c6..7086c62f81711 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -25,7 +25,12 @@ describe('set signal status', () => { let clients = clientsServiceMock.createClients(); beforeEach(() => { + // jest carries state between mocked implementations when using + // spyOn. So now we're doing all three of these. + // https://github.com/facebook/jest/issues/7136#issuecomment-565976599 jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); jest.spyOn(myUtils, 'getIndex').mockReturnValue('fakeindex'); server = createMockServer(); @@ -50,6 +55,15 @@ describe('set signal status', () => { const { statusCode } = await server.inject(getSetSignalStatusByQueryRequest()); expect(statusCode).toBe(200); }); + + test('catches error if callAsCurrentUser throws error', async () => { + clients.clusterClient.callAsCurrentUser.mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(getSetSignalStatusByQueryRequest()); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index dd3b8d3c99e0c..ee3fd349a26ee 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -28,7 +28,7 @@ export const setSignalsStatusRouteDef = ( payload: setSignalsStatusSchema, }, }, - async handler(request: SignalsStatusRequest) { + async handler(request: SignalsStatusRequest, headers) { const { signal_ids: signalIds, query, status } = request.payload; const { clusterClient, spacesClient } = await getClients(request); const index = getIndex(spacesClient.getSpaceId, config); @@ -45,7 +45,7 @@ export const setSignalsStatusRouteDef = ( }; } try { - return clusterClient.callAsCurrentUser('updateByQuery', { + const updateByQueryResponse = await clusterClient.callAsCurrentUser('updateByQuery', { index, body: { script: { @@ -56,9 +56,15 @@ export const setSignalsStatusRouteDef = ( }, ignoreUnavailable: true, }); - } catch (exc) { - // error while getting or updating signal with id: id in signal index .siem-signals - return transformError(exc); + return updateByQueryResponse; + } catch (err) { + const error = transformError(err); + return headers + .response({ + message: error.message, + status_code: error.statusCode, + }) + .code(error.statusCode); } }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts index 9439adfcec3cb..210ac9f3d7b01 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts @@ -127,5 +127,18 @@ describe('query for signal', () => { const { statusCode } = await server.inject(request); expect(statusCode).toBe(400); }); + test('catches error if deleteRules throws error', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: { ...typicalSignalsQueryAggs(), ...typicalSignalsQuery() }, + }; + clients.clusterClient.callAsCurrentUser.mockImplementation(async () => { + throw new Error('Test error'); + }); + const { payload, statusCode } = await server.inject(request); + expect(JSON.parse(payload).message).toBe('Test error'); + expect(statusCode).toBe(500); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index adb6e5f32921a..7636329ecc306 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -28,21 +28,27 @@ export const querySignalsRouteDef = ( payload: querySignalsSchema, }, }, - async handler(request: SignalsQueryRequest) { + async handler(request: SignalsQueryRequest, headers) { const { query, aggs, _source, track_total_hits, size } = request.payload; const { clusterClient, spacesClient } = await getClients(request); const index = getIndex(spacesClient.getSpaceId, config); try { - return clusterClient.callAsCurrentUser('search', { + const searchSignalsIndexResult = await clusterClient.callAsCurrentUser('search', { index, body: { query, aggs, _source, track_total_hits, size }, ignoreUnavailable: true, }); - } catch (exc) { - // error while getting or updating signal with id: id in signal index .siem-signals - return transformError(exc); + return searchSignalsIndexResult; + } catch (err) { + const error = transformError(err); + return headers + .response({ + message: error.message, + status_code: error.statusCode, + }) + .code(error.statusCode); } }, }; 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 957ddd4ee6caa..3148083b4db26 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 @@ -15,6 +15,7 @@ import { ImportSuccessError, createImportErrorObject, transformImportError, + convertToSnakeCase, } from './utils'; import { createMockConfig } from './__mocks__'; @@ -312,4 +313,15 @@ describe('utils', () => { expect(index).toEqual('mockSignalsIndex-myspace'); }); }); + + describe('convertToSnakeCase', () => { + it('converts camelCase to snakeCase', () => { + const values = { myTestCamelCaseKey: 'something' }; + expect(convertToSnakeCase(values)).toEqual({ my_test_camel_case_key: 'something' }); + }); + it('returns empty object when object is empty', () => { + const values = {}; + expect(convertToSnakeCase(values)).toEqual({}); + }); + }); }); 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 55832ab67dc6b..36e1a814d8ec2 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 @@ -5,6 +5,7 @@ */ import Boom from 'boom'; +import { snakeCase } from 'lodash/fp'; import { APP_ID, SIGNALS_INDEX_KEY } from '../../../../common/constants'; import { LegacyServices } from '../../../types'; @@ -211,3 +212,11 @@ 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 => { + return Object.keys(obj).reduce((acc, item) => { + const newKey = snakeCase(item); + return { ...acc, [newKey]: obj[item] }; + }, {}); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index d4b7c252e3e38..b1dc62f6fc90f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -5,12 +5,10 @@ */ import { Readable } from 'stream'; import { createRulesStreamFromNdJson } from './create_rules_stream_from_ndjson'; -import { createPromiseFromStreams, createConcatStream } from 'src/legacy/utils/streams'; +import { createPromiseFromStreams } from 'src/legacy/utils/streams'; import { ImportRuleAlertRest } from '../types'; -const readStreamToCompletion = (stream: Readable) => { - return createPromiseFromStreams([stream, createConcatStream([])]); -}; +type PromiseFromStreams = ImportRuleAlertRest | Error; export const getOutputSample = (): Partial => ({ rule_id: 'rule-1', @@ -43,8 +41,11 @@ describe('create_rules_stream_from_ndjson', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); - const result = await readStreamToCompletion(rulesObjectsStream); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const result = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); expect(result).toEqual([ { rule_id: 'rule-1', @@ -95,6 +96,22 @@ describe('create_rules_stream_from_ndjson', () => { ]); }); + test('returns error when ndjson stream is larger than limit', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push(getSampleAsNdjson(sample2)); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(1); + await expect( + createPromiseFromStreams([ndJsonStream, ...rulesObjectsStream]) + ).rejects.toThrowError("Can't import more than 1 rules"); + }); + test('skips empty lines', async () => { const sample1 = getOutputSample(); const sample2 = getOutputSample(); @@ -108,8 +125,11 @@ describe('create_rules_stream_from_ndjson', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); - const result = await readStreamToCompletion(rulesObjectsStream); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const result = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); expect(result).toEqual([ { rule_id: 'rule-1', @@ -172,8 +192,11 @@ describe('create_rules_stream_from_ndjson', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); - const result = await readStreamToCompletion(rulesObjectsStream); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const result = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); expect(result).toEqual([ { rule_id: 'rule-1', @@ -236,8 +259,11 @@ describe('create_rules_stream_from_ndjson', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); - const result = await readStreamToCompletion(rulesObjectsStream); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const result = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); const resultOrError = result as Error[]; expect(resultOrError[0]).toEqual({ rule_id: 'rule-1', @@ -300,8 +326,11 @@ describe('create_rules_stream_from_ndjson', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); - const result = await readStreamToCompletion(rulesObjectsStream); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const result = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); const resultOrError = result as TypeError[]; expect(resultOrError[0]).toEqual({ rule_id: 'rule-1', @@ -366,8 +395,11 @@ describe('create_rules_stream_from_ndjson', () => { this.push(null); }, }); - const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); - const result = await readStreamToCompletion(rulesObjectsStream); + const rulesObjectsStream = createRulesStreamFromNdJson(1000); + const result = await createPromiseFromStreams([ + ndJsonStream, + ...rulesObjectsStream, + ]); const resultOrError = result as TypeError[]; expect(resultOrError[1] instanceof TypeError).toEqual(true); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts index 6d58171a3245d..ae0dfa20852aa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Readable, Transform } from 'stream'; +import { Transform } from 'stream'; import { has, isString } from 'lodash/fp'; import { ImportRuleAlertRest } from '../types'; import { @@ -74,15 +74,13 @@ export const createLimitStream = (limit: number): Transform => { * Inspiration and the pattern of code followed is from: * saved_objects/lib/create_saved_objects_stream_from_ndjson.ts */ -export const createRulesStreamFromNdJson = ( - ndJsonStream: Readable, - ruleLimit: number -): Transform => { - return ndJsonStream - .pipe(createSplitStream('\n')) - .pipe(parseNdjsonStrings()) - .pipe(filterExportedCounts()) - .pipe(validateRules()) - .pipe(createLimitStream(ruleLimit)) - .pipe(createConcatStream([])); +export const createRulesStreamFromNdJson = (ruleLimit: number) => { + return [ + createSplitStream('\n'), + parseNdjsonStrings(), + filterExportedCounts(), + validateRules(), + createLimitStream(ruleLimit), + createConcatStream([]), + ]; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 98f5df4852530..44d3013263c65 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -10,9 +10,15 @@ import { getFindResultWithSingleHit, FindHit, } from '../routes/__mocks__/request_responses'; +import * as readRules from './read_rules'; import { alertsClientMock } from '../../../../../../../plugins/alerting/server/mocks'; describe('get_export_by_object_ids', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); describe('getExportByObjectIds', () => { test('it exports object ids into an expected string with new line characters', async () => { const alertsClient = alertsClientMock.create(); @@ -119,6 +125,23 @@ describe('get_export_by_object_ids', () => { expect(exports).toEqual(expected); }); + test('it returns error when readRules throws error', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + jest.spyOn(readRules, 'readRules').mockImplementation(async () => { + throw new Error('Test error'); + }); + const objects = [{ rule_id: 'rule-1' }]; + const exports = await getRulesFromObjects(alertsClient, objects); + const expected: RulesErrors = { + exportedCount: 0, + missingRules: [{ rule_id: objects[0].rule_id }], + rules: [], + }; + expect(exports).toEqual(expected); + }); + test('it does not transform the rule if the rule is an immutable rule and designates it as a missing rule', async () => { const alertsClient = alertsClientMock.create(); const result = getResult(); 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 45507a69f50c2..aa1cce6f15238 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 @@ -8,7 +8,23 @@ import { readRules } from './read_rules'; import { alertsClientMock } from '../../../../../../../plugins/alerting/server/mocks'; import { getResult, getFindResultWithSingleHit } from '../routes/__mocks__/request_responses'; +class TestError extends Error { + constructor() { + // Pass remaining arguments (including vendor specific ones) to parent constructor + super(); + + this.name = 'CustomError'; + this.output = { statusCode: 404 }; + } + public output: { statusCode: number }; +} + describe('read_rules', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); describe('readRules', () => { test('should return the output from alertsClient if id is set but ruleId is undefined', async () => { const alertsClient = alertsClientMock.create(); @@ -21,6 +37,49 @@ describe('read_rules', () => { }); expect(rule).toEqual(getResult()); }); + 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 rule = await readRules({ + alertsClient, + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + ruleId: undefined, + }); + expect(rule).toEqual(null); + }); + + test('should return error if alerts client throws 404 error on get', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.get.mockImplementation(() => { + throw new TestError(); + }); + + const rule = await readRules({ + alertsClient, + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + ruleId: undefined, + }); + expect(rule).toEqual(null); + }); + + test('should return error if alerts client throws error on get', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.get.mockImplementation(() => { + throw new Error('Test error'); + }); + try { + await readRules({ + alertsClient, + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + ruleId: undefined, + }); + } catch (exc) { + expect(exc.message).toEqual('Test error'); + } + }); test('should return the output from alertsClient if id is set but ruleId is null', async () => { const alertsClient = alertsClientMock.create(); @@ -47,6 +106,20 @@ describe('read_rules', () => { expect(rule).toEqual(getResult()); }); + 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: [] }); + + const rule = await readRules({ + alertsClient, + id: undefined, + ruleId: 'rule-1', + }); + expect(rule).toEqual(null); + }); + test('should return the output from alertsClient if id is null but ruleId is set', async () => { const alertsClient = alertsClientMock.create(); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts index cbe6dbda8449f..94e4e6357a4a0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts @@ -31,7 +31,7 @@ export const readRules = async ({ return null; } } catch (err) { - if (err.output.statusCode === 404) { + if (err?.output?.statusCode === 404) { return null; } else { // throw non-404 as they would be 500 or other internal errors diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index fa22765c143e1..3d95e9868a1d6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -197,10 +197,6 @@ export const isAlertType = (obj: unknown): obj is RuleAlertType => { return get('alertTypeId', obj) === SIGNALS_ID; }; -export const isRuleStatusAttributes = (obj: unknown): obj is IRuleStatusAttributes => { - return get('lastSuccessMessage', obj) != null; -}; - export const isRuleStatusSavedObjectType = ( obj: unknown ): obj is SavedObject => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signal_index_exists.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signal_index_exists.sh index b4a494a102b54..ec3e0595c7b08 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signal_index_exists.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signal_index_exists.sh @@ -10,7 +10,7 @@ set -e ./check_env_variables.sh # Example: ./signal_index_exists.sh -curl -s -k --head \ +curl -s -k -f \ -H 'Content-Type: application/json' \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${KIBANA_URL}${SPACE_URL}/api/detection_engine/index + ${KIBANA_URL}${SPACE_URL}/api/detection_engine/index > /dev/null diff --git a/x-pack/legacy/plugins/upgrade_assistant/index.ts b/x-pack/legacy/plugins/upgrade_assistant/index.ts index 3f98ff60a91ce..b5e8ce4750215 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/index.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/index.ts @@ -3,25 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; -import Joi from 'joi'; import { Legacy } from 'kibana'; -import { resolve } from 'path'; import mappings from './mappings.json'; -import { plugin } from './server/np_ready'; -import { CloudSetup } from '../../../plugins/cloud/server'; export function upgradeAssistant(kibana: any) { - const publicSrc = resolve(__dirname, 'public'); - const npSrc = resolve(publicSrc, 'np_ready'); - const config: Legacy.PluginSpecOptions = { id: 'upgrade_assistant', - configPrefix: 'xpack.upgrade_assistant', - require: ['elasticsearch'], uiExports: { // @ts-ignore - managementSections: ['plugins/upgrade_assistant'], savedObjectSchemas: { 'upgrade-assistant-reindex-operation': { isNamespaceAgnostic: true, @@ -30,41 +19,10 @@ export function upgradeAssistant(kibana: any) { isNamespaceAgnostic: true, }, }, - styleSheetPaths: resolve(npSrc, 'application/index.scss'), mappings, }, - publicDir: publicSrc, - - config() { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - - init(server: Legacy.Server) { - // Add server routes and initialize the plugin here - const instance = plugin({} as any); - const { usageCollection, cloud } = server.newPlatform.setup.plugins; - instance.setup(server.newPlatform.setup.core, { - usageCollection, - cloud: cloud as CloudSetup, - __LEGACY: { - // Legacy objects - events: server.events, - savedObjects: server.savedObjects, - - // Legacy functions - log: server.log.bind(server), - - // Legacy plugins - plugins: { - elasticsearch: server.plugins.elasticsearch, - xpack_main: server.plugins.xpack_main, - }, - }, - }); - }, + init() {}, }; return new kibana.Plugin(config); } diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/legacy.ts b/x-pack/legacy/plugins/upgrade_assistant/public/legacy.ts deleted file mode 100644 index b6bc6a14de224..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/legacy.ts +++ /dev/null @@ -1,106 +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 { ComponentType } from 'react'; -import { i18n } from '@kbn/i18n'; - -/* LEGACY IMPORTS */ -import { npSetup } from 'ui/new_platform'; -import { wrapInI18nContext } from 'ui/i18n'; -import { management } from 'ui/management'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import routes from 'ui/routes'; -import chrome from 'ui/chrome'; -/* LEGACY IMPORTS */ - -import { NEXT_MAJOR_VERSION } from '../common/version'; -import { plugin } from './np_ready'; -import { CloudSetup } from '../../../../plugins/cloud/public'; - -const BASE_PATH = `/management/elasticsearch/upgrade_assistant`; - -export interface LegacyAppMountParameters { - __LEGACY: { renderToElement: (RootComponent: ComponentType) => void }; -} - -export interface LegacyApp { - mount(ctx: any, params: LegacyAppMountParameters): void; -} - -export interface LegacyManagementPlugin { - sections: { - get( - name: string - ): { - registerApp(app: LegacyApp): void; - }; - }; -} - -// Based on /rfcs/text/0006_management_section_service.md -export interface LegacyPlugins { - cloud?: CloudSetup; - management: LegacyManagementPlugin; - __LEGACY: { - XSRF: string; - }; -} - -function startApp() { - routes.when(`${BASE_PATH}/:view?`, { - template: - '', - }); - const { cloud } = npSetup.plugins as any; - const legacyPluginsShim: LegacyPlugins = { - cloud: cloud as CloudSetup, - __LEGACY: { - XSRF: chrome.getXsrfToken(), - }, - management: { - sections: { - get(_: string) { - return { - registerApp(app) { - management.getSection('elasticsearch').register('upgrade_assistant', { - visible: true, - display: i18n.translate('xpack.upgradeAssistant.appTitle', { - defaultMessage: '{version} Upgrade Assistant', - values: { version: `${NEXT_MAJOR_VERSION}.0` }, - }), - order: 100, - url: `#${BASE_PATH}`, - }); - - app.mount( - {}, - { - __LEGACY: { - // While there is not an NP API for registering management section apps yet - renderToElement: RootComponent => { - uiModules - .get('kibana') - .directive('upgradeAssistant', (reactDirective: any) => { - return reactDirective(wrapInI18nContext(RootComponent)); - }); - }, - }, - } - ); - }, - }; - }, - }, - }, - }; - - const pluginInstance = plugin(); - - pluginInstance.setup(npSetup.core, legacyPluginsShim); -} - -startApp(); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app.tsx deleted file mode 100644 index 571967ab114c9..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import { EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { NEXT_MAJOR_VERSION } from '../../../common/version'; -import { UpgradeAssistantTabs } from './components/tabs'; -import { AppContextProvider, ContextValue, AppContext } from './app_context'; - -type AppDependencies = ContextValue; - -export const RootComponent = (deps: AppDependencies) => { - return ( - -
- - - -

- -

-
-
-
- - {({ http }) => } - -
-
- ); -}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts deleted file mode 100644 index dc7a758839fe5..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.mocks.ts +++ /dev/null @@ -1,24 +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 { ReindexStatus, ReindexStep } from '../../../../../../../../common/types'; - -export const mockClient = { - post: jest.fn().mockResolvedValue({ - lastCompletedStep: ReindexStep.created, - status: ReindexStatus.inProgress, - }), - get: jest.fn().mockResolvedValue({ - status: 200, - data: { - warnings: [], - reindexOp: null, - }, - }), -}; -jest.mock('axios', () => ({ - create: jest.fn().mockReturnValue(mockClient), -})); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/plugin.ts b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/plugin.ts deleted file mode 100644 index ed85b988c25d6..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/plugin.ts +++ /dev/null @@ -1,24 +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 { Plugin, CoreSetup } from 'src/core/public'; -import { RootComponent } from './application/app'; -import { LegacyPlugins } from '../legacy'; - -export class UpgradeAssistantUIPlugin implements Plugin { - async setup({ http }: CoreSetup, { cloud, management, __LEGACY: { XSRF } }: LegacyPlugins) { - const appRegistrar = management.sections.get('kibana'); - const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); - - return appRegistrar.registerApp({ - mount(__, { __LEGACY: { renderToElement } }) { - return renderToElement(() => RootComponent({ http, XSRF, isCloudEnabled })); - }, - }); - } - async start() {} - async stop() {} -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.ts deleted file mode 100644 index b52b3b812b7f9..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.ts +++ /dev/null @@ -1,47 +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 { - UIOpen, - UIOpenOption, - UPGRADE_ASSISTANT_DOC_ID, - UPGRADE_ASSISTANT_TYPE, -} from '../../../../common/types'; -import { RequestShim, ServerShim } from '../../types'; - -async function incrementUIOpenOptionCounter(server: ServerShim, uiOpenOptionCounter: UIOpenOption) { - const { getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - - await internalRepository.incrementCounter( - UPGRADE_ASSISTANT_TYPE, - UPGRADE_ASSISTANT_DOC_ID, - `ui_open.${uiOpenOptionCounter}` - ); -} - -export async function upsertUIOpenOption(server: ServerShim, req: RequestShim): Promise { - const { overview, cluster, indices } = req.payload as UIOpen; - - if (overview) { - await incrementUIOpenOptionCounter(server, 'overview'); - } - - if (cluster) { - await incrementUIOpenOptionCounter(server, 'cluster'); - } - - if (indices) { - await incrementUIOpenOptionCounter(server, 'indices'); - } - - return { - overview, - cluster, - indices, - }; -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.ts deleted file mode 100644 index 626d51b298e72..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - UIReindex, - UIReindexOption, - UPGRADE_ASSISTANT_DOC_ID, - UPGRADE_ASSISTANT_TYPE, -} from '../../../../common/types'; -import { RequestShim, ServerShim } from '../../types'; - -async function incrementUIReindexOptionCounter( - server: ServerShim, - uiOpenOptionCounter: UIReindexOption -) { - const { getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - - await internalRepository.incrementCounter( - UPGRADE_ASSISTANT_TYPE, - UPGRADE_ASSISTANT_DOC_ID, - `ui_reindex.${uiOpenOptionCounter}` - ); -} - -export async function upsertUIReindexOption( - server: ServerShim, - req: RequestShim -): Promise { - const { close, open, start, stop } = req.payload as UIReindex; - - if (close) { - await incrementUIReindexOptionCounter(server, 'close'); - } - - if (open) { - await incrementUIReindexOptionCounter(server, 'open'); - } - - if (start) { - await incrementUIReindexOptionCounter(server, 'start'); - } - - if (stop) { - await incrementUIReindexOptionCounter(server, 'stop'); - } - - return { - close, - open, - start, - stop, - }; -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts deleted file mode 100644 index fae369fa59394..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts +++ /dev/null @@ -1,49 +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 { Plugin, CoreSetup, CoreStart } from 'src/core/server'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { ServerShim, ServerShimWithRouter } from './types'; -import { credentialStoreFactory } from './lib/reindexing/credential_store'; -import { registerUpgradeAssistantUsageCollector } from './lib/telemetry'; -import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; -import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; -import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/reindex_indices'; -import { CloudSetup } from '../../../../../plugins/cloud/server'; -import { registerTelemetryRoutes } from './routes/telemetry'; - -interface PluginsSetup { - __LEGACY: ServerShim; - usageCollection: UsageCollectionSetup; - cloud?: CloudSetup; -} - -export class UpgradeAssistantServerPlugin implements Plugin { - setup({ http }: CoreSetup, { __LEGACY, usageCollection, cloud }: PluginsSetup) { - const router = http.createRouter(); - const shimWithRouter: ServerShimWithRouter = { ...__LEGACY, router }; - registerClusterCheckupRoutes(shimWithRouter, { cloud }); - registerDeprecationLoggingRoutes(shimWithRouter); - - // The ReindexWorker uses a map of request headers that contain the authentication credentials - // for a given reindex. We cannot currently store these in an the .kibana index b/c we do not - // want to expose these credentials to any unauthenticated users. We also want to avoid any need - // to add a user for a special index just for upgrading. This in-memory cache allows us to - // process jobs without the browser staying on the page, but will require that jobs go into - // a paused state if no Kibana nodes have the required credentials. - const credentialStore = credentialStoreFactory(); - - const worker = registerReindexWorker(__LEGACY, credentialStore); - registerReindexIndicesRoutes(shimWithRouter, worker, credentialStore); - - // Bootstrap the needed routes and the collector for the telemetry - registerTelemetryRoutes(shimWithRouter); - registerUpgradeAssistantUsageCollector(usageCollection, __LEGACY); - } - - start(core: CoreStart, plugins: any) {} - - stop(): void {} -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.ts deleted file mode 100644 index 81cf690d813ad..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.ts +++ /dev/null @@ -1,46 +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 _ from 'lodash'; -import { ServerShimWithRouter } from '../types'; -import { getUpgradeAssistantStatus } from '../lib/es_migration_apis'; -import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; -import { CloudSetup } from '../../../../../../plugins/cloud/server'; -import { createRequestShim } from './create_request_shim'; - -interface PluginsSetup { - cloud?: CloudSetup; -} - -export function registerClusterCheckupRoutes( - server: ServerShimWithRouter, - pluginsSetup: PluginsSetup -) { - const { cloud } = pluginsSetup; - const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - - server.router.get( - { - path: '/api/upgrade_assistant/status', - validate: false, - }, - versionCheckHandlerWrapper(async (ctx, request, response) => { - const reqShim = createRequestShim(request); - try { - return response.ok({ - body: await getUpgradeAssistantStatus(callWithRequest, reqShim, isCloudEnabled), - }); - } catch (e) { - if (e.status === 403) { - return response.forbidden(e.message); - } - - return response.internalError({ body: e }); - } - }) - ); -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/create_request_shim.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/create_request_shim.ts deleted file mode 100644 index b1a5c8b72d0e0..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/create_request_shim.ts +++ /dev/null @@ -1,16 +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 { KibanaRequest } from 'kibana/server'; -import { RequestShim } from '../types'; - -export const createRequestShim = (req: KibanaRequest): RequestShim => { - return { - headers: req.headers as Record, - payload: req.body || (req as any).payload, - params: req.params, - }; -}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.ts deleted file mode 100644 index 7e19ef3fb6047..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.ts +++ /dev/null @@ -1,57 +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 { schema } from '@kbn/config-schema'; - -import { - getDeprecationLoggingStatus, - setDeprecationLogging, -} from '../lib/es_deprecation_logging_apis'; -import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; -import { ServerShimWithRouter } from '../types'; -import { createRequestShim } from './create_request_shim'; - -export function registerDeprecationLoggingRoutes(server: ServerShimWithRouter) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - - server.router.get( - { - path: '/api/upgrade_assistant/deprecation_logging', - validate: false, - }, - versionCheckHandlerWrapper(async (ctx, request, response) => { - const reqShim = createRequestShim(request); - try { - const result = await getDeprecationLoggingStatus(callWithRequest, reqShim); - return response.ok({ body: result }); - } catch (e) { - return response.internalError({ body: e }); - } - }) - ); - - server.router.put( - { - path: '/api/upgrade_assistant/deprecation_logging', - validate: { - body: schema.object({ - isEnabled: schema.boolean(), - }), - }, - }, - versionCheckHandlerWrapper(async (ctx, request, response) => { - const reqShim = createRequestShim(request); - try { - const { isEnabled } = reqShim.payload as { isEnabled: boolean }; - return response.ok({ - body: await setDeprecationLogging(callWithRequest, reqShim, isEnabled), - }); - } catch (e) { - return response.internalError({ body: e }); - } - }) - ); -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.ts deleted file mode 100644 index c22f12316bd02..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.ts +++ /dev/null @@ -1,207 +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 { schema } from '@kbn/config-schema'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { SavedObjectsClientContract } from 'kibana/server'; -import { ReindexStatus } from '../../../common/types'; -import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; -import { reindexServiceFactory, ReindexWorker } from '../lib/reindexing'; -import { CredentialStore } from '../lib/reindexing/credential_store'; -import { reindexActionsFactory } from '../lib/reindexing/reindex_actions'; -import { ServerShim, ServerShimWithRouter } from '../types'; -import { createRequestShim } from './create_request_shim'; - -export function registerReindexWorker(server: ServerShim, credentialStore: CredentialStore) { - const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster( - 'admin' - ); - const xpackInfo = server.plugins.xpack_main.info; - const savedObjectsRepository = server.savedObjects.getSavedObjectsRepository( - callWithInternalUser - ); - const savedObjectsClient = new server.savedObjects.SavedObjectsClient( - savedObjectsRepository - ) as SavedObjectsClientContract; - - // Cannot pass server.log directly because it's value changes during startup (?). - // Use this function to proxy through. - const log = (tags: string | string[], data?: string | object | (() => any), timestamp?: number) => - server.log(tags, data, timestamp); - - const worker = new ReindexWorker( - savedObjectsClient, - credentialStore, - callWithRequest, - callWithInternalUser, - xpackInfo, - log - ); - - // Wait for ES connection before starting the polling loop. - server.plugins.elasticsearch.waitUntilReady().then(() => { - worker.start(); - server.events.on('stop', () => worker.stop()); - }); - - return worker; -} - -export function registerReindexIndicesRoutes( - server: ServerShimWithRouter, - worker: ReindexWorker, - credentialStore: CredentialStore -) { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - const xpackInfo = server.plugins.xpack_main.info; - const BASE_PATH = '/api/upgrade_assistant/reindex'; - - // Start reindex for an index - server.router.post( - { - path: `${BASE_PATH}/{indexName}`, - validate: { - params: schema.object({ - indexName: schema.string(), - }), - }, - }, - versionCheckHandlerWrapper(async (ctx, request, response) => { - const reqShim = createRequestShim(request); - const { indexName } = reqShim.params; - const { client } = ctx.core.savedObjects; - const callCluster = callWithRequest.bind(null, reqShim) as CallCluster; - const reindexActions = reindexActionsFactory(client, callCluster); - const reindexService = reindexServiceFactory( - callCluster, - xpackInfo, - reindexActions, - server.log - ); - - try { - if (!(await reindexService.hasRequiredPrivileges(indexName))) { - return response.forbidden({ - body: `You do not have adequate privileges to reindex this index.`, - }); - } - - const existingOp = await reindexService.findReindexOperation(indexName); - - // If the reindexOp already exists and it's paused, resume it. Otherwise create a new one. - const reindexOp = - existingOp && existingOp.attributes.status === ReindexStatus.paused - ? await reindexService.resumeReindexOperation(indexName) - : await reindexService.createReindexOperation(indexName); - - // Add users credentials for the worker to use - credentialStore.set(reindexOp, reqShim.headers); - - // Kick the worker on this node to immediately pickup the new reindex operation. - worker.forceRefresh(); - - return response.ok({ body: reindexOp.attributes }); - } catch (e) { - return response.internalError({ body: e }); - } - }) - ); - - // Get status - server.router.get( - { - path: `${BASE_PATH}/{indexName}`, - validate: { - params: schema.object({ - indexName: schema.string(), - }), - }, - }, - versionCheckHandlerWrapper(async (ctx, request, response) => { - const reqShim = createRequestShim(request); - const { client } = ctx.core.savedObjects; - const { indexName } = reqShim.params; - const callCluster = callWithRequest.bind(null, reqShim) as CallCluster; - const reindexActions = reindexActionsFactory(client, callCluster); - const reindexService = reindexServiceFactory( - callCluster, - xpackInfo, - reindexActions, - server.log - ); - - try { - const hasRequiredPrivileges = await reindexService.hasRequiredPrivileges(indexName); - const reindexOp = await reindexService.findReindexOperation(indexName); - // If the user doesn't have privileges than querying for warnings is going to fail. - const warnings = hasRequiredPrivileges - ? await reindexService.detectReindexWarnings(indexName) - : []; - const indexGroup = reindexService.getIndexGroup(indexName); - - return response.ok({ - body: { - reindexOp: reindexOp ? reindexOp.attributes : null, - warnings, - indexGroup, - hasRequiredPrivileges, - }, - }); - } catch (e) { - if (!e.isBoom) { - return response.internalError({ body: e }); - } - return response.customError({ - body: { - message: e.message, - }, - statusCode: e.statusCode, - }); - } - }) - ); - - // Cancel reindex - server.router.post( - { - path: `${BASE_PATH}/{indexName}/cancel`, - validate: { - params: schema.object({ - indexName: schema.string(), - }), - }, - }, - versionCheckHandlerWrapper(async (ctx, request, response) => { - const reqShim = createRequestShim(request); - const { indexName } = reqShim.params; - const { client } = ctx.core.savedObjects; - const callCluster = callWithRequest.bind(null, reqShim) as CallCluster; - const reindexActions = reindexActionsFactory(client, callCluster); - const reindexService = reindexServiceFactory( - callCluster, - xpackInfo, - reindexActions, - server.log - ); - - try { - await reindexService.cancelReindexing(indexName); - - return response.ok({ body: { acknowledged: true } }); - } catch (e) { - if (!e.isBoom) { - return response.internalError({ body: e }); - } - return response.customError({ - body: { - message: e.message, - }, - statusCode: e.statusCode, - }); - } - }) - ); -} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/types.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/types.ts deleted file mode 100644 index 77ba97529c32f..0000000000000 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/types.ts +++ /dev/null @@ -1,29 +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 { Legacy } from 'kibana'; -import { IRouter } from 'src/core/server'; -import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; -import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; - -export interface ServerShim { - plugins: { - elasticsearch: ElasticsearchPlugin; - xpack_main: XPackMainPlugin; - }; - log: any; - events: any; - savedObjects: Legacy.SavedObjectsService; -} - -export interface ServerShimWithRouter extends ServerShim { - router: IRouter; -} - -export interface RequestShim { - headers: Record; - payload: any; - params: any; -} diff --git a/x-pack/legacy/plugins/uptime/common/constants/plugin.ts b/x-pack/legacy/plugins/uptime/common/constants/plugin.ts index f6fa569a50315..00781726941d5 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/plugin.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/plugin.ts @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; + export const PLUGIN = { APP_ROOT_ID: 'react-uptime-root', DESCRIPTION: 'Uptime monitoring', ID: 'uptime', - ROUTER_BASE_NAME: '/app/uptime#', LOCAL_STORAGE_KEY: 'xpack.uptime', + NAME: i18n.translate('xpack.uptime.featureRegistry.uptimeFeatureName', { + defaultMessage: 'Uptime', + }), + ROUTER_BASE_NAME: '/app/uptime#', TITLE: 'uptime', }; diff --git a/x-pack/legacy/plugins/uptime/index.ts b/x-pack/legacy/plugins/uptime/index.ts index cf7332f97d466..feecef5857895 100644 --- a/x-pack/legacy/plugins/uptime/index.ts +++ b/x-pack/legacy/plugins/uptime/index.ts @@ -6,9 +6,7 @@ import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; -import { PluginInitializerContext } from 'src/core/server'; import { PLUGIN } from './common/constants'; -import { KibanaServer, plugin } from './server'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const uptime = (kibana: any) => @@ -35,21 +33,4 @@ export const uptime = (kibana: any) => }, home: ['plugins/uptime/register_feature'], }, - init(server: KibanaServer) { - const initializerContext = {} as PluginInitializerContext; - const { savedObjects } = server; - const { xpack_main } = server.plugins; - const { usageCollection } = server.newPlatform.setup.plugins; - - plugin(initializerContext).setup( - { - route: server.newPlatform.setup.core.http.createRouter(), - }, - { - savedObjects, - usageCollection, - xpack: xpack_main, - } - ); - }, }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap index ebbd8a4ac56a8..db41dfb0b04c4 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap @@ -5,7 +5,6 @@ exports[`Snapshot component renders without errors 1`] = ` loading={false} >

- All monitors are up + 23 + + Monitors

`; @@ -15,7 +17,9 @@ exports[`SnapshotHeading renders custom heading for no monitors 1`] = ` size="s" >

- No monitors found + 0 + + Monitors

`; @@ -25,7 +29,9 @@ exports[`SnapshotHeading renders standard heading for valid counts 1`] = ` size="s" >

- 3/17 monitors are down + 17 + + Monitors

`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx index 5ddef3d0aabd1..70d082b26d653 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx @@ -10,17 +10,17 @@ import { SnapshotHeading } from '../snapshot_heading'; describe('SnapshotHeading', () => { it('renders custom heading for no down monitors', () => { - const wrapper = shallowWithIntl(); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); it('renders standard heading for valid counts', () => { - const wrapper = shallowWithIntl(); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); it('renders custom heading for no monitors', () => { - const wrapper = shallowWithIntl(); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/chart_wrapper.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/chart_wrapper.test.tsx.snap index c1b5970f6456c..71690432fd01b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/chart_wrapper.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/chart_wrapper.test.tsx.snap @@ -114,7 +114,6 @@ exports[`ChartWrapper component renders the component with loading false 1`] = ` } > { it('renders the component with loading false', () => { const component = shallowWithIntl( - + @@ -29,7 +29,7 @@ describe('ChartWrapper component', () => { it('renders the component with loading true', () => { const component = shallowWithIntl( - + @@ -40,7 +40,7 @@ describe('ChartWrapper component', () => { it('mounts the component with loading true or false', async () => { const component = mount( - + @@ -62,7 +62,7 @@ describe('ChartWrapper component', () => { it('mounts the component with chart when loading true or false', async () => { const component = mount( - + diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx index 8531cd1a3cc83..999ade9dccdd9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx @@ -6,7 +6,6 @@ import { EuiSpacer } from '@elastic/eui'; import React from 'react'; -import { get } from 'lodash'; import { DonutChart } from './charts'; import { ChartWrapper } from './charts/chart_wrapper'; import { SnapshotHeading } from './snapshot_heading'; @@ -28,11 +27,11 @@ interface SnapshotComponentProps { */ export const SnapshotComponent: React.FC = ({ count, height, loading }) => ( - (count, 'down', 0)} total={get(count, 'total', 0)} /> + (count, 'up', 0)} - down={get(count, 'down', 0)} + up={count.up} + down={count.down} height={SNAPSHOT_CHART_HEIGHT} width={SNAPSHOT_CHART_WIDTH} /> diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx index 85d1294d4b064..308d6e19241c2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx @@ -8,32 +8,17 @@ import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -interface Props { - down: number; - total: number; -} +export const SnapshotHeading = ({ total }: { total: number }) => { + const monitorsText = + total === 1 + ? i18n.translate('xpack.uptime.snapshot.monitor', { defaultMessage: 'Monitor' }) + : i18n.translate('xpack.uptime.snapshot.monitors', { defaultMessage: 'Monitors' }); -const getMessage = (down: number, total: number): string => { - if (down === 0 && total > 0) { - return i18n.translate('xpack.uptime.snapshot.zeroDownMessage', { - defaultMessage: 'All monitors are up', - }); - } else if (down === 0 && total === 0) { - return i18n.translate('xpack.uptime.snapshot.noMonitorMessage', { - defaultMessage: 'No monitors found', - }); - } - return i18n.translate('xpack.uptime.snapshot.downCountsMessage', { - defaultMessage: '{down}/{total} monitors are down', - values: { - down, - total, - }, - }); + return ( + +

+ {total} {monitorsText} +

+
+ ); }; - -export const SnapshotHeading = ({ down, total }: Props) => ( - -

{getMessage(down, total)}

-
-); diff --git a/x-pack/legacy/plugins/uptime/scripts/graphql_schemas.ts b/x-pack/legacy/plugins/uptime/scripts/graphql_schemas.ts deleted file mode 100644 index c337cf098e48d..0000000000000 --- a/x-pack/legacy/plugins/uptime/scripts/graphql_schemas.ts +++ /dev/null @@ -1,15 +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 { buildSchemaFromTypeDefinitions } from 'graphql-tools'; -import { typeDefs } from '../server/graphql'; - -export const schemas = [...typeDefs]; - -// this default export is used to feed the combined types to the gql-gen tool -// which generates the corresponding typescript types -// eslint-disable-next-line import/no-default-export -export default buildSchemaFromTypeDefinitions(schemas); diff --git a/x-pack/legacy/plugins/uptime/server/plugin.ts b/x-pack/legacy/plugins/uptime/server/plugin.ts deleted file mode 100644 index acecce305e7cb..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/plugin.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PluginInitializerContext } from 'src/core/server'; -import { initServerWithKibana } from './kibana.index'; -import { UptimeCoreSetup, UptimeCorePlugins } from './lib/adapters/framework'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(); -} - -export class Plugin { - public setup(core: UptimeCoreSetup, plugins: UptimeCorePlugins) { - initServerWithKibana(core, plugins); - } -} diff --git a/x-pack/package.json b/x-pack/package.json index f76b0182ea228..b8fe0326903b6 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -106,6 +106,7 @@ "@types/uuid": "^3.4.4", "@types/xml-crypto": "^1.4.0", "@types/xml2js": "^0.4.5", + "@welldone-software/why-did-you-render": "^4.0.0", "abab": "^1.0.4", "axios": "^0.19.0", "babel-jest": "^24.9.0", diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts index 62406cfaf66e1..01355f2a34f92 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts @@ -12,8 +12,6 @@ import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { withoutControlCharacters } from './lib/string_utils'; -const ACTION_NAME = 'Server log'; - // params definition export type ActionParamsType = TypeOf; @@ -37,7 +35,9 @@ const ParamsSchema = schema.object({ export function getActionType({ logger }: { logger: Logger }): ActionType { return { id: '.server-log', - name: ACTION_NAME, + name: i18n.translate('xpack.actions.builtin.serverLogTitle', { + defaultMessage: 'Server log', + }), validate: { params: ParamsSchema, }, @@ -56,7 +56,7 @@ async function executor( const sanitizedMessage = withoutControlCharacters(params.message); try { - logger[params.level](`${ACTION_NAME}: ${sanitizedMessage}`); + logger[params.level](`Server log: ${sanitizedMessage}`); } catch (err) { const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', { defaultMessage: 'error logging message', diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts index a6c43f48fa803..9ae96cb23a5c3 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts @@ -49,7 +49,7 @@ beforeAll(() => { describe('get()', () => { test('should return correct action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('servicenow'); + expect(actionType.name).toEqual('ServiceNow'); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow.ts index 2d5c18207def3..0ad435281eba4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow.ts @@ -89,7 +89,9 @@ export function getActionType({ }): ActionType { return { id: '.servicenow', - name: 'servicenow', + name: i18n.translate('xpack.actions.builtin.servicenowTitle', { + defaultMessage: 'ServiceNow', + }), validate: { config: schema.object(ConfigSchemaProps, { validate: curry(validateConfig)(configurationUtilities), diff --git a/x-pack/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/plugins/apm/server/lib/helpers/es_client.ts index 86eb1dba507f0..c22084dbb7168 100644 --- a/x-pack/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/plugins/apm/server/lib/helpers/es_client.ts @@ -12,8 +12,9 @@ import { IndicesCreateParams, DeleteDocumentResponse } from 'elasticsearch'; -import { cloneDeep, isString, merge, uniqueId } from 'lodash'; +import { cloneDeep, isString, merge } from 'lodash'; import { KibanaRequest } from 'src/core/server'; +import chalk from 'chalk'; import { ESSearchRequest, ESSearchResponse @@ -126,6 +127,10 @@ interface ClientCreateOptions { export type ESClient = ReturnType; +function formatObj(obj: Record) { + return JSON.stringify(obj, null, 2); +} + export function getESClient( context: APMRequestHandlerContext, request: KibanaRequest, @@ -136,25 +141,49 @@ export function getESClient( callAsInternalUser } = context.core.elasticsearch.dataClient; - const callMethod = clientAsInternalUser - ? callAsInternalUser - : callAsCurrentUser; + async function callEs(operationName: string, params: Record) { + const startTime = process.hrtime(); + + let res: any; + let esError = null; + try { + res = clientAsInternalUser + ? await callAsInternalUser(operationName, params) + : await callAsCurrentUser(operationName, params); + } catch (e) { + // catch error and throw after outputting debug info + esError = e; + } + + if (context.params.query._debug) { + const highlightColor = esError ? 'bgRed' : 'inverse'; + const diff = process.hrtime(startTime); + const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`; + const routeInfo = `${request.route.method.toUpperCase()} ${ + request.route.path + }`; - const debug = context.params.query._debug; + console.log( + chalk.bold[highlightColor](`=== Debug: ${routeInfo} (${duration}) ===`) + ); + + if (operationName === 'search') { + console.log(`GET ${params.index}/_${operationName}`); + console.log(formatObj(params.body)); + } else { + console.log(chalk.bold('ES operation:'), operationName); - function withTime( - fn: (log: typeof console.log) => Promise - ): Promise { - const log = console.log.bind(console, uniqueId()); - if (!debug) { - return fn(log); + console.log(chalk.bold('ES query:')); + console.log(formatObj(params)); + } + console.log(`\n`); } - const time = process.hrtime(); - return fn(log).then(data => { - const now = process.hrtime(time); - log(`took: ${Math.round(now[0] * 1000 + now[1] / 1e6)}ms`); - return data; - }); + + if (esError) { + throw esError; + } + + return res; } return { @@ -171,40 +200,25 @@ export function getESClient( apmOptions ); - return withTime(log => { - if (context.params.query._debug) { - log(`--DEBUG ES QUERY--`); - log( - `${request.url.pathname} ${JSON.stringify(context.params.query)}` - ); - log(`GET ${nextParams.index}/_search`); - log(JSON.stringify(nextParams.body, null, 2)); - } - - return (callMethod('search', nextParams) as unknown) as Promise< - ESSearchResponse - >; - }); + return callEs('search', nextParams); }, index: (params: APMIndexDocumentParams) => { - return withTime(() => callMethod('index', params)); + return callEs('index', params); }, delete: (params: IndicesDeleteParams): Promise => { - return withTime(() => callMethod('delete', params)); + return callEs('delete', params); }, indicesCreate: (params: IndicesCreateParams) => { - return withTime(() => callMethod('indices.create', params)); + return callEs('indices.create', params); }, hasPrivileges: ( params: IndexPrivilegesParams ): Promise => { - return withTime(() => - callMethod('transport.request', { - method: 'POST', - path: '/_security/user/_has_privileges', - body: params - }) - ); + return callEs('transport.request', { + method: 'POST', + path: '/_security/user/_has_privileges', + body: params + }); } }; } diff --git a/x-pack/plugins/data_enhanced/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts index 93b6b7a957182..927716aae9780 100644 --- a/x-pack/plugins/data_enhanced/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -9,3 +9,5 @@ import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plug export const plugin = () => new DataEnhancedPlugin(); export { DataEnhancedSetup, DataEnhancedStart }; + +export { ASYNC_SEARCH_STRATEGY, IAsyncSearchRequest, IAsyncSearchOptions } from './search'; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 14b5382bc85aa..4fe27d400f45f 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -8,6 +8,7 @@ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; +import { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './search'; export interface DataEnhancedSetupDependencies { data: DataPublicPluginSetup; @@ -20,11 +21,14 @@ export type DataEnhancedSetup = ReturnType; export type DataEnhancedStart = ReturnType; export class DataEnhancedPlugin implements Plugin { - public setup(core: CoreSetup, plugins: DataEnhancedSetupDependencies) { - plugins.data.autocomplete.addQuerySuggestionProvider( + constructor() {} + + public setup(core: CoreSetup, { data }: DataEnhancedSetupDependencies) { + data.autocomplete.addQuerySuggestionProvider( KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider(core) ); + data.search.registerSearchStrategyProvider(ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider); } public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts new file mode 100644 index 0000000000000..95f2c9e477064 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { of } from 'rxjs'; +import { AbortController } from 'abort-controller'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { asyncSearchStrategyProvider } from './async_search_strategy'; +import { IAsyncSearchOptions } from './types'; +import { CoreStart } from 'kibana/public'; + +describe('Async search strategy', () => { + let mockCoreStart: MockedKeys; + const mockSearch = jest.fn(); + const mockRequest = { params: {}, serverStrategy: 'foo' }; + const mockOptions: IAsyncSearchOptions = { pollInterval: 0 }; + + beforeEach(() => { + mockCoreStart = coreMock.createStart(); + mockSearch.mockReset(); + }); + + it('only sends one request if the first response is complete', async () => { + mockSearch.mockReturnValueOnce(of({ id: 1, total: 1, loaded: 1 })); + + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }); + + await asyncSearch.search(mockRequest, mockOptions).toPromise(); + + expect(mockSearch.mock.calls[0][0]).toEqual(mockRequest); + expect(mockSearch.mock.calls[0][1]).toEqual({}); + expect(mockSearch).toBeCalledTimes(1); + }); + + it('stops polling when the response is complete', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }); + + expect(mockSearch).toBeCalledTimes(0); + + await asyncSearch.search(mockRequest, mockOptions).toPromise(); + + expect(mockSearch).toBeCalledTimes(2); + }); + + it('only sends the ID and server strategy after the first request', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }); + + expect(mockSearch).toBeCalledTimes(0); + + await asyncSearch.search(mockRequest, mockOptions).toPromise(); + + expect(mockSearch).toBeCalledTimes(2); + expect(mockSearch.mock.calls[0][0]).toEqual(mockRequest); + expect(mockSearch.mock.calls[1][0]).toEqual({ id: 1, serverStrategy: 'foo' }); + }); + + it('sends a DELETE request and stops polling when the signal is aborted', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }); + const abortController = new AbortController(); + const options = { ...mockOptions, signal: abortController.signal }; + + const promise = asyncSearch.search(mockRequest, options).toPromise(); + abortController.abort(); + + try { + await promise; + } catch (e) { + expect(e.name).toBe('AbortError'); + expect(mockSearch).toBeCalledTimes(1); + expect(mockCoreStart.http.delete).toBeCalled(); + } + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts new file mode 100644 index 0000000000000..fa5d677a53b2a --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EMPTY, fromEvent, NEVER, Observable, throwError, timer } from 'rxjs'; +import { mergeMap, expand, takeUntil } from 'rxjs/operators'; +import { + IKibanaSearchResponse, + ISearchContext, + ISearchStrategy, + SYNC_SEARCH_STRATEGY, + TSearchStrategyProvider, +} from '../../../../../src/plugins/data/public'; +import { IAsyncSearchRequest, IAsyncSearchOptions } from './types'; + +export const ASYNC_SEARCH_STRATEGY = 'ASYNC_SEARCH_STRATEGY'; + +declare module '../../../../../src/plugins/data/public' { + export interface IRequestTypesMap { + [ASYNC_SEARCH_STRATEGY]: IAsyncSearchRequest; + } +} + +export const asyncSearchStrategyProvider: TSearchStrategyProvider = ( + context: ISearchContext +): ISearchStrategy => { + const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); + const { search } = syncStrategyProvider(context); + return { + search: ( + request: IAsyncSearchRequest, + { pollInterval = 1000, ...options }: IAsyncSearchOptions = {} + ): Observable => { + const { serverStrategy } = request; + let id: string | undefined = request.id; + + const aborted$ = options.signal + ? fromEvent(options.signal, 'abort').pipe( + mergeMap(() => { + // If we haven't received the response to the initial request, including the ID, then + // we don't need to send a follow-up request to delete this search. Otherwise, we + // send the follow-up request to delete this search, then throw an abort error. + if (id !== undefined) { + context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); + } + + const error = new Error('Aborted'); + error.name = 'AbortError'; + return throwError(error); + }) + ) + : NEVER; + + return search(request, options).pipe( + expand(response => { + // If the response indicates it is complete, stop polling and complete the observable + if ((response.loaded ?? 0) >= (response.total ?? 0)) return EMPTY; + + id = response.id; + + // Delay by the given poll interval + return timer(pollInterval).pipe( + // Send future requests using just the ID from the response + mergeMap(() => { + return search({ id, serverStrategy }, options); + }) + ); + }), + takeUntil(aborted$) + ); + }, + }; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/index.ts b/x-pack/plugins/data_enhanced/public/search/index.ts new file mode 100644 index 0000000000000..a7729aeea5647 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './async_search_strategy'; +export { IAsyncSearchRequest, IAsyncSearchOptions } from './types'; diff --git a/x-pack/plugins/data_enhanced/public/search/types.ts b/x-pack/plugins/data_enhanced/public/search/types.ts new file mode 100644 index 0000000000000..edaaf1b22654d --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/types.ts @@ -0,0 +1,21 @@ +/* + * 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 { ISearchOptions, ISyncSearchRequest } from '../../../../../src/plugins/data/public'; + +export interface IAsyncSearchRequest extends ISyncSearchRequest { + /** + * The ID received from the response from the initial request + */ + id?: string; +} + +export interface IAsyncSearchOptions extends ISearchOptions { + /** + * The number of milliseconds to wait between receiving a response and sending another request + */ + pollInterval?: number; +} diff --git a/x-pack/plugins/drilldowns/public/actions/open_flyout_add_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx similarity index 61% rename from x-pack/plugins/drilldowns/public/actions/open_flyout_add_drilldown/index.tsx rename to x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx index 06f134b10a4b7..0b9f54f51f61e 100644 --- a/x-pack/plugins/drilldowns/public/actions/open_flyout_add_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx @@ -10,11 +10,11 @@ import { CoreStart } from 'src/core/public'; import { Action } from '../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -import { FormCreateDrilldown } from '../../components/form_create_drilldown'; +import { FlyoutCreateDrilldown } from '../../components/flyout_create_drilldown'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; -interface ActionContext { +export interface FlyoutCreateDrilldownActionContext { embeddable: IEmbeddable; } @@ -22,29 +22,31 @@ export interface OpenFlyoutAddDrilldownParams { overlays: () => Promise; } -export class OpenFlyoutAddDrilldown implements Action { +export class FlyoutCreateDrilldownAction implements Action { public readonly type = OPEN_FLYOUT_ADD_DRILLDOWN; public readonly id = OPEN_FLYOUT_ADD_DRILLDOWN; - public order = 100; + public order = 5; constructor(protected readonly params: OpenFlyoutAddDrilldownParams) {} public getDisplayName() { - return i18n.translate('xpack.drilldowns.panel.openFlyoutAddDrilldown.displayName', { - defaultMessage: 'Add drilldown', + return i18n.translate('xpack.drilldowns.FlyoutCreateDrilldownAction.displayName', { + defaultMessage: 'Create Drilldown', }); } public getIconType() { - return 'empty'; + return 'plusInCircle'; } - public async isCompatible({ embeddable }: ActionContext) { + public async isCompatible({ embeddable }: FlyoutCreateDrilldownActionContext) { return true; } - public async execute({ embeddable }: ActionContext) { + public async execute(context: FlyoutCreateDrilldownActionContext) { const overlays = await this.params.overlays(); - overlays.openFlyout(toMountPoint()); + const handle = overlays.openFlyout( + toMountPoint( handle.close()} />) + ); } } diff --git a/x-pack/plugins/drilldowns/public/actions/index.ts b/x-pack/plugins/drilldowns/public/actions/index.ts index c0ca7fac22049..ce235043b4ef6 100644 --- a/x-pack/plugins/drilldowns/public/actions/index.ts +++ b/x-pack/plugins/drilldowns/public/actions/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './open_flyout_add_drilldown'; +export * from './flyout_create_drilldown'; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/__examples__/drilldown_hello_bar.examples.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx similarity index 91% rename from x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/__examples__/drilldown_hello_bar.examples.tsx rename to x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx index afa82f5e74c16..7a9e19342f27c 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/__examples__/drilldown_hello_bar.examples.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; -import { DrilldownHelloBar } from '..'; +import { DrilldownHelloBar } from '.'; storiesOf('components/DrilldownHelloBar', module).add('default', () => { return ; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx new file mode 100644 index 0000000000000..1ef714f7b86e2 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export interface DrilldownHelloBarProps { + docsLink?: string; +} + +/** + * @todo https://github.com/elastic/kibana/issues/55311 + */ +export const DrilldownHelloBar: React.FC = ({ docsLink }) => { + return ( +
+

+ Drilldowns provide the ability to define a new behavior when interacting with a panel. You + can add multiple options or simply override the default filtering behavior. +

+ View docs +
+ ); +}; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/index.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/index.tsx index 895a100df3ac5..f28c8cfa3a059 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/index.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/index.tsx @@ -4,24 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; - -export interface DrilldownHelloBarProps { - docsLink?: string; -} - -export const DrilldownHelloBar: React.FC = ({ docsLink }) => { - return ( -
-

- Drilldowns provide the ability to define a new behavior when interacting with a panel. You - can add multiple options or simply override the default filtering behavior. -

- View docs - -
- ); -}; +export * from './drilldown_hello_bar'; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/__examples__/drilldown_picker.examples.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx similarity index 91% rename from x-pack/plugins/drilldowns/public/components/drilldown_picker/__examples__/drilldown_picker.examples.tsx rename to x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx index dfdd9627ab5cd..5627a5d6f4522 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_picker/__examples__/drilldown_picker.examples.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; -import { DrilldownPicker } from '..'; +import { DrilldownPicker } from '.'; storiesOf('components/DrilldownPicker', module).add('default', () => { return ; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx new file mode 100644 index 0000000000000..3748fc666c81c --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +// eslint-disable-next-line +export interface DrilldownPickerProps {} + +export const DrilldownPicker: React.FC = () => { + return ( + + ); +}; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx index 3748fc666c81c..3be289fe6d46e 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx @@ -4,18 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; - -// eslint-disable-next-line -export interface DrilldownPickerProps {} - -export const DrilldownPicker: React.FC = () => { - return ( - - ); -}; +export * from './drilldown_picker'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx new file mode 100644 index 0000000000000..4f024b7d9cd6a --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FlyoutCreateDrilldown } from '.'; + +storiesOf('components/FlyoutCreateDrilldown', module) + .add('default', () => { + return ; + }) + .add('open in flyout', () => { + return ( + + + + ); + }); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx new file mode 100644 index 0000000000000..b45ac9197c7e0 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { FormCreateDrilldown } from '../form_create_drilldown'; +import { FlyoutFrame } from '../flyout_frame'; +import { txtCreateDrilldown } from './i18n'; +import { FlyoutCreateDrilldownActionContext } from '../../actions'; + +export interface FlyoutCreateDrilldownProps { + context: FlyoutCreateDrilldownActionContext; + onClose?: () => void; +} + +export const FlyoutCreateDrilldown: React.FC = ({ + context, + onClose, +}) => { + const footer = ( + {}} fill> + {txtCreateDrilldown} + + ); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts new file mode 100644 index 0000000000000..ceabc6d3a9aa5 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts @@ -0,0 +1,14 @@ +/* + * 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'; + +export const txtCreateDrilldown = i18n.translate( + 'xpack.drilldowns.components.FlyoutCreateDrilldown.CreateDrilldown', + { + defaultMessage: 'Create drilldown', + } +); diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/find_potential_matches_test.ts b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts similarity index 84% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/find_potential_matches_test.ts rename to x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts index 41bc2aa258807..ce235043b4ef6 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/find_potential_matches_test.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts @@ -3,3 +3,5 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +export * from './flyout_create_drilldown'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx new file mode 100644 index 0000000000000..2715637f6392f --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx @@ -0,0 +1,39 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +import * as React from 'react'; +import { EuiFlyout, EuiButton } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FlyoutFrame } from '.'; + +storiesOf('components/FlyoutFrame', module) + .add('default', () => { + return test; + }) + .add('with title', () => { + return test; + }) + .add('with onClose', () => { + return console.log('onClose')}>test; + }) + .add('custom footer', () => { + return click me!}>test; + }) + .add('open in flyout', () => { + return ( + + Save} + onClose={() => console.log('onClose')} + > + test + + + ); + }); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx new file mode 100644 index 0000000000000..b5fb52fcf5c18 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render } from 'react-dom'; +import { render as renderTestingLibrary, fireEvent } from '@testing-library/react'; +import { FlyoutFrame } from '.'; + +describe('', () => { + test('renders without crashing', () => { + const div = document.createElement('div'); + render(, div); + }); + + describe('[title=]', () => { + test('renders title in

tag', () => { + const div = document.createElement('div'); + render(, div); + + const title = div.querySelector('h1'); + expect(title?.textContent).toBe('foobar'); + }); + + test('title can be any react node', () => { + const div = document.createElement('div'); + render( + + foo bar + + } + />, + div + ); + + const title = div.querySelector('h1'); + expect(title?.innerHTML).toBe('foo bar'); + }); + }); + + describe('[footer=]', () => { + test('if [footer=] prop not provided, does not render footer', () => { + const div = document.createElement('div'); + render(, div); + + const footer = div.querySelector('[data-test-subj="flyoutFooter"]'); + expect(footer).toBe(null); + }); + + test('can render anything in footer', () => { + const div = document.createElement('div'); + render( + + a b + + } + />, + div + ); + + const footer = div.querySelector('[data-test-subj="flyoutFooter"]'); + expect(footer?.innerHTML).toBe('a b'); + }); + }); + + describe('[onClose=]', () => { + test('does not render close button if "onClose" prop is missing', () => { + const div = document.createElement('div'); + render(, div); + + const closeButton = div.querySelector('[data-test-subj="flyoutCloseButton"]'); + expect(closeButton).toBe(null); + }); + + test('renders close button if "onClose" prop is provided', () => { + const div = document.createElement('div'); + render( {}} />, div); + + const closeButton = div.querySelector('[data-test-subj="flyoutCloseButton"]'); + expect(closeButton).not.toBe(null); + }); + + test('calls onClose prop when close button clicked', async () => { + const onClose = jest.fn(); + const el = renderTestingLibrary(); + + const closeButton = el.queryByText('Close'); + + expect(onClose).toHaveBeenCalledTimes(0); + + fireEvent.click(closeButton!); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx new file mode 100644 index 0000000000000..2945cfd739482 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, +} from '@elastic/eui'; +import { txtClose } from './i18n'; + +export interface FlyoutFrameProps { + title?: React.ReactNode; + footer?: React.ReactNode; + onClose?: () => void; +} + +/** + * @todo This component can be moved to `kibana_react`. + */ +export const FlyoutFrame: React.FC = ({ + title = '', + footer, + onClose, + children, +}) => { + const headerFragment = title && ( + + +

{title}

+
+
+ ); + + const footerFragment = (onClose || footer) && ( + + + + {onClose && ( + + {txtClose} + + )} + + + {footer} + + + + ); + + return ( + <> + {headerFragment} + {children} + {footerFragment} + + ); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts similarity index 61% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/index.ts rename to x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts index 020d6972f8280..257d7d36dbee1 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/index.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UpgradeAssistantUIPlugin } from './plugin'; +import { i18n } from '@kbn/i18n'; -export const plugin = () => { - return new UpgradeAssistantUIPlugin(); -}; +export const txtClose = i18n.translate('xpack.drilldowns.components.FlyoutFrame.Close', { + defaultMessage: 'Close', +}); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_frame/index.tsx similarity index 88% rename from x-pack/legacy/plugins/upgrade_assistant/public/index.ts rename to x-pack/plugins/drilldowns/public/components/flyout_frame/index.tsx index d22b5d64b6b46..040b4b6b5e243 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/index.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './legacy'; +export * from './flyout_frame'; diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/__examples__/form_create_drilldown.examples.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/__examples__/form_create_drilldown.examples.tsx deleted file mode 100644 index 34f6932b41dac..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/__examples__/form_create_drilldown.examples.tsx +++ /dev/null @@ -1,13 +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 * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import { FormCreateDrilldown } from '..'; - -storiesOf('components/FormCreateDrilldown', module).add('default', () => { - return ; -}); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx new file mode 100644 index 0000000000000..e7e1d67473e8c --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx @@ -0,0 +1,34 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FormCreateDrilldown } from '.'; + +const DemoEditName: React.FC = () => { + const [name, setName] = React.useState(''); + + return ; +}; + +storiesOf('components/FormCreateDrilldown', module) + .add('default', () => { + return ; + }) + .add('[name=foobar]', () => { + return ; + }) + .add('can edit name', () => ) + .add('open in flyout', () => { + return ( + + + + ); + }); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx new file mode 100644 index 0000000000000..6691966e47e64 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render } from 'react-dom'; +import { FormCreateDrilldown } from '.'; +import { render as renderTestingLibrary, fireEvent } from '@testing-library/react'; +import { txtNameOfDrilldown } from './i18n'; + +describe('', () => { + test('renders without crashing', () => { + const div = document.createElement('div'); + render( {}} />, div); + }); + + describe('[name=]', () => { + test('if name not provided, uses to empty string', () => { + const div = document.createElement('div'); + + render(, div); + + const input = div.querySelector( + '[data-test-subj="dynamicActionNameInput"]' + ) as HTMLInputElement; + + expect(input?.value).toBe(''); + }); + + test('can set name input field value', () => { + const div = document.createElement('div'); + + render(, div); + + const input = div.querySelector( + '[data-test-subj="dynamicActionNameInput"]' + ) as HTMLInputElement; + + expect(input?.value).toBe('foo'); + + render(, div); + + expect(input?.value).toBe('bar'); + }); + + test('fires onNameChange callback on name change', () => { + const onNameChange = jest.fn(); + const utils = renderTestingLibrary( + + ); + const input = utils.getByLabelText(txtNameOfDrilldown); + + expect(onNameChange).toHaveBeenCalledTimes(0); + + fireEvent.change(input, { target: { value: 'qux' } }); + + expect(onNameChange).toHaveBeenCalledTimes(1); + expect(onNameChange).toHaveBeenCalledWith('qux'); + + fireEvent.change(input, { target: { value: 'quxx' } }); + + expect(onNameChange).toHaveBeenCalledTimes(2); + expect(onNameChange).toHaveBeenCalledWith('quxx'); + }); + }); +}); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx new file mode 100644 index 0000000000000..4422de604092b --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiForm, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { DrilldownHelloBar } from '../drilldown_hello_bar'; +import { txtNameOfDrilldown, txtUntitledDrilldown, txtDrilldownAction } from './i18n'; +import { DrilldownPicker } from '../drilldown_picker'; + +const noop = () => {}; + +export interface FormCreateDrilldownProps { + name?: string; + onNameChange?: (name: string) => void; +} + +export const FormCreateDrilldown: React.FC = ({ + name = '', + onNameChange = noop, +}) => { + const nameFragment = ( + + onNameChange(event.target.value)} + data-test-subj="dynamicActionNameInput" + /> + + ); + + const triggerPicker =
Trigger Picker will be here
; + const actionPicker = ( + + + + ); + + return ( + <> + + {nameFragment} + {triggerPicker} + {actionPicker} + + ); +}; diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts index 922131ba4b901..4c0e287935edd 100644 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts @@ -7,21 +7,21 @@ import { i18n } from '@kbn/i18n'; export const txtNameOfDrilldown = i18n.translate( - 'xpack.drilldowns.components.form_create_drilldown.nameOfDrilldown', + 'xpack.drilldowns.components.FormCreateDrilldown.nameOfDrilldown', { defaultMessage: 'Name of drilldown', } ); export const txtUntitledDrilldown = i18n.translate( - 'xpack.drilldowns.components.form_create_drilldown.untitledDrilldown', + 'xpack.drilldowns.components.FormCreateDrilldown.untitledDrilldown', { defaultMessage: 'Untitled drilldown', } ); export const txtDrilldownAction = i18n.translate( - 'xpack.drilldowns.components.form_create_drilldown.drilldownAction', + 'xpack.drilldowns.components.FormCreateDrilldown.drilldownAction', { defaultMessage: 'Drilldown action', } diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx index 40cd4cf2b210b..c2c5a7e435b39 100644 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx @@ -4,27 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiForm, EuiFormRow, EuiFieldText } from '@elastic/eui'; -import { DrilldownHelloBar } from '../drilldown_hello_bar'; -import { txtNameOfDrilldown, txtUntitledDrilldown, txtDrilldownAction } from './i18n'; -import { DrilldownPicker } from '../drilldown_picker'; - -// eslint-disable-next-line -export interface FormCreateDrilldownProps {} - -export const FormCreateDrilldown: React.FC = () => { - return ( -
- - - - - - - - - -
- ); -}; +export * from './form_create_drilldown'; diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts index f22f452181648..b1310ba003ff7 100644 --- a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts @@ -5,17 +5,17 @@ */ import { CoreSetup } from 'src/core/public'; -import { OpenFlyoutAddDrilldown } from '../actions/open_flyout_add_drilldown'; +import { FlyoutCreateDrilldownAction } from '../actions'; import { DrilldownsSetupDependencies } from '../plugin'; export class DrilldownService { bootstrap(core: CoreSetup, { uiActions }: DrilldownsSetupDependencies) { - const actionOpenFlyoutAddDrilldown = new OpenFlyoutAddDrilldown({ + const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays: async () => (await core.getStartServices())[0].overlays, }); - uiActions.registerAction(actionOpenFlyoutAddDrilldown); - uiActions.attachAction('CONTEXT_MENU_TRIGGER', actionOpenFlyoutAddDrilldown.id); + uiActions.registerAction(actionFlyoutCreateDrilldown); + uiActions.attachAction('CONTEXT_MENU_TRIGGER', actionFlyoutCreateDrilldown.id); } /** diff --git a/x-pack/plugins/drilldowns/scripts/storybook.js b/x-pack/plugins/drilldowns/scripts/storybook.js index 9b0f57746e584..2bfd0eb1a8f19 100644 --- a/x-pack/plugins/drilldowns/scripts/storybook.js +++ b/x-pack/plugins/drilldowns/scripts/storybook.js @@ -9,5 +9,5 @@ import { join } from 'path'; // eslint-disable-next-line require('@kbn/storybook').runStorybookCli({ name: 'drilldowns', - storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.examples.tsx')], + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], }); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index ecefd4bfa271e..b61196439ee4f 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -173,6 +173,7 @@ describe('createIndex', () => { await clusterClientAdapter.createIndex('foo'); expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.create', { index: 'foo', + body: {}, }); }); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index c74eeacc9bb19..d585fd4f539b5 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -94,9 +94,12 @@ export class ClusterClientAdapter { return result as boolean; } - public async createIndex(name: string): Promise { + public async createIndex(name: string, body: any = {}): Promise { try { - await this.callEs('indices.create', { index: name }); + await this.callEs('indices.create', { + index: name, + body, + }); } catch (err) { if (err.body?.error?.type !== 'resource_already_exists_exception') { throw new Error(`error creating initial index: ${err.message}`); diff --git a/x-pack/plugins/event_log/server/es/documents.test.ts b/x-pack/plugins/event_log/server/es/documents.test.ts index 7edca4b3943a6..c08d0ac978bc9 100644 --- a/x-pack/plugins/event_log/server/es/documents.test.ts +++ b/x-pack/plugins/event_log/server/es/documents.test.ts @@ -22,8 +22,7 @@ describe('getIndexTemplate()', () => { test('returns the correct details of the index template', () => { const indexTemplate = getIndexTemplate(esNames); - expect(indexTemplate.index_patterns).toEqual([esNames.indexPattern]); - expect(indexTemplate.aliases[esNames.alias]).toEqual({}); + expect(indexTemplate.index_patterns).toEqual([esNames.indexPatternWithVersion]); expect(indexTemplate.settings.number_of_shards).toBeGreaterThanOrEqual(0); expect(indexTemplate.settings.number_of_replicas).toBeGreaterThanOrEqual(0); expect(indexTemplate.settings['index.lifecycle.name']).toBe(esNames.ilmPolicy); diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts index 09dd7383c4c5e..982454e671008 100644 --- a/x-pack/plugins/event_log/server/es/documents.ts +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -10,10 +10,7 @@ import mappings from '../../generated/mappings.json'; // returns the body of an index template used in an ES indices.putTemplate call export function getIndexTemplate(esNames: EsNames) { const indexTemplateBody: any = { - index_patterns: [esNames.indexPattern], - aliases: { - [esNames.alias]: {}, - }, + index_patterns: [esNames.indexPatternWithVersion], settings: { number_of_shards: 1, number_of_replicas: 1, diff --git a/x-pack/plugins/event_log/server/es/init.ts b/x-pack/plugins/event_log/server/es/init.ts index 7094277f7aa9f..c67d6541ce002 100644 --- a/x-pack/plugins/event_log/server/es/init.ts +++ b/x-pack/plugins/event_log/server/es/init.ts @@ -62,7 +62,13 @@ class EsInitializationSteps { async createInitialIndexIfNotExists(): Promise { const exists = await this.esContext.esAdapter.doesAliasExist(this.esContext.esNames.alias); if (!exists) { - await this.esContext.esAdapter.createIndex(this.esContext.esNames.initialIndex); + await this.esContext.esAdapter.createIndex(this.esContext.esNames.initialIndex, { + aliases: { + [this.esContext.esNames.alias]: { + is_write_index: true, + }, + }, + }); } } } diff --git a/x-pack/plugins/event_log/server/es/names.mock.ts b/x-pack/plugins/event_log/server/es/names.mock.ts index 7b013a0d263da..268421235b4b2 100644 --- a/x-pack/plugins/event_log/server/es/names.mock.ts +++ b/x-pack/plugins/event_log/server/es/names.mock.ts @@ -9,11 +9,12 @@ import { EsNames } from './names'; const createNamesMock = () => { const mock: jest.Mocked = { base: '.kibana', - alias: '.kibana-event-log', + alias: '.kibana-event-log-8.0.0', ilmPolicy: '.kibana-event-log-policy', indexPattern: '.kibana-event-log-*', - initialIndex: '.kibana-event-log-000001', - indexTemplate: '.kibana-event-log-template', + indexPatternWithVersion: '.kibana-event-log-8.0.0-*', + initialIndex: '.kibana-event-log-8.0.0-000001', + indexTemplate: '.kibana-event-log-8.0.0-template', }; return mock; }; diff --git a/x-pack/plugins/event_log/server/es/names.test.ts b/x-pack/plugins/event_log/server/es/names.test.ts index d88c4212df91c..baefd756bb1ed 100644 --- a/x-pack/plugins/event_log/server/es/names.test.ts +++ b/x-pack/plugins/event_log/server/es/names.test.ts @@ -6,15 +6,21 @@ import { getEsNames } from './names'; +jest.mock('../lib/../../../../package.json', () => ({ + version: '1.2.3', +})); + describe('getEsNames()', () => { test('works as expected', () => { const base = 'XYZ'; + const version = '1.2.3'; const esNames = getEsNames(base); expect(esNames.base).toEqual(base); - expect(esNames.alias).toEqual(`${base}-event-log`); + expect(esNames.alias).toEqual(`${base}-event-log-${version}`); expect(esNames.ilmPolicy).toEqual(`${base}-event-log-policy`); expect(esNames.indexPattern).toEqual(`${base}-event-log-*`); - expect(esNames.initialIndex).toEqual(`${base}-event-log-000001`); - expect(esNames.indexTemplate).toEqual(`${base}-event-log-template`); + expect(esNames.indexPatternWithVersion).toEqual(`${base}-event-log-${version}-*`); + expect(esNames.initialIndex).toEqual(`${base}-event-log-${version}-000001`); + expect(esNames.indexTemplate).toEqual(`${base}-event-log-${version}-template`); }); }); diff --git a/x-pack/plugins/event_log/server/es/names.ts b/x-pack/plugins/event_log/server/es/names.ts index be737d23625f1..d55d02a16fc9a 100644 --- a/x-pack/plugins/event_log/server/es/names.ts +++ b/x-pack/plugins/event_log/server/es/names.ts @@ -4,25 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -const EVENT_LOG_NAME_SUFFIX = '-event-log'; +import xPackage from '../../../../package.json'; + +const EVENT_LOG_NAME_SUFFIX = `-event-log`; +const EVENT_LOG_VERSION_SUFFIX = `-${xPackage.version}`; export interface EsNames { base: string; alias: string; ilmPolicy: string; indexPattern: string; + indexPatternWithVersion: string; initialIndex: string; indexTemplate: string; } export function getEsNames(baseName: string): EsNames { const eventLogName = `${baseName}${EVENT_LOG_NAME_SUFFIX}`; + const eventLogNameWithVersion = `${eventLogName}${EVENT_LOG_VERSION_SUFFIX}`; return { base: baseName, - alias: eventLogName, + alias: eventLogNameWithVersion, ilmPolicy: `${eventLogName}-policy`, indexPattern: `${eventLogName}-*`, - initialIndex: `${eventLogName}-000001`, - indexTemplate: `${eventLogName}-template`, + indexPatternWithVersion: `${eventLogNameWithVersion}-*`, + initialIndex: `${eventLogNameWithVersion}-000001`, + indexTemplate: `${eventLogNameWithVersion}-template`, }; } diff --git a/x-pack/plugins/infra/public/apps/start_app.tsx b/x-pack/plugins/infra/public/apps/start_app.tsx index 66e699abd22b4..a797e4c9d4ba7 100644 --- a/x-pack/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/plugins/infra/public/apps/start_app.tsx @@ -26,6 +26,7 @@ import { KibanaContextProvider, } from '../../../../../src/plugins/kibana_react/public'; import { AppRouter } from '../routers'; +import '../index.scss'; export const CONTAINER_CLASSNAME = 'infra-container-element'; @@ -74,12 +75,8 @@ export async function startApp( ); - // Ensure the element we're handed from application mounting takes up - // the full size it can, so that our inner application styles work as - // expected. - element.style.height = '100%'; - element.style.display = 'flex'; - element.style.overflowY = 'hidden'; // Prevent having scroll within a container having scroll. It messes up with drag-n-drop elements + // Ensure the element we're handed from application mounting is assigned a class + // for our index.scss styles to apply to. element.className += ` ${CONTAINER_CLASSNAME}`; ReactDOM.render(, element); diff --git a/x-pack/plugins/infra/public/components/source_configuration/index.ts b/x-pack/plugins/infra/public/components/source_configuration/index.ts index 4879a53ca329d..98825567cc204 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/index.ts +++ b/x-pack/plugins/infra/public/components/source_configuration/index.ts @@ -5,7 +5,4 @@ */ export { SourceConfigurationSettings } from './source_configuration_settings'; -export { - ViewSourceConfigurationButton, - ViewSourceConfigurationButtonHrefBase, -} from './view_source_configuration_button'; +export { ViewSourceConfigurationButton } from './view_source_configuration_button'; diff --git a/x-pack/plugins/infra/public/components/source_configuration/view_source_configuration_button.tsx b/x-pack/plugins/infra/public/components/source_configuration/view_source_configuration_button.tsx index 9b584b2ef3bd0..9c3a40fb7ecf0 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/view_source_configuration_button.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/view_source_configuration_button.tsx @@ -8,23 +8,16 @@ import { EuiButton } from '@elastic/eui'; import React from 'react'; import { Route } from 'react-router-dom'; -export enum ViewSourceConfigurationButtonHrefBase { - infrastructure = 'infrastructure', - logs = 'logs', -} - interface ViewSourceConfigurationButtonProps { 'data-test-subj'?: string; - hrefBase: ViewSourceConfigurationButtonHrefBase; children: React.ReactNode; } export const ViewSourceConfigurationButton = ({ 'data-test-subj': dataTestSubj, - hrefBase, children, }: ViewSourceConfigurationButtonProps) => { - const href = `/${hrefBase}/settings`; + const href = '/settings'; return ( { {uiCapabilities?.infrastructure?.configureSource ? ( - + {i18n.translate('xpack.infra.configureSourceActionLabel', { defaultMessage: 'Change source configuration', })} diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx index 1294007240027..739bad5689a96 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_no_indices_content.tsx @@ -10,10 +10,7 @@ import { identity } from 'fp-ts/lib/function'; import React from 'react'; import { NoIndices } from '../../../components/empty_states/no_indices'; -import { - ViewSourceConfigurationButton, - ViewSourceConfigurationButtonHrefBase, -} from '../../../components/source_configuration'; +import { ViewSourceConfigurationButton } from '../../../components/source_configuration'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; export const LogsPageNoIndicesContent = () => { @@ -49,10 +46,7 @@ export const LogsPageNoIndicesContent = () => { {canConfigureSource ? ( - + {i18n.translate('xpack.infra.configureSourceActionLabel', { defaultMessage: 'Change source configuration', })} diff --git a/x-pack/plugins/infra/public/pages/metrics/components/invalid_node.tsx b/x-pack/plugins/infra/public/pages/metrics/components/invalid_node.tsx index fde3b61de50b5..43f684cd5a585 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/invalid_node.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/invalid_node.tsx @@ -10,10 +10,7 @@ import { identity } from 'fp-ts/lib/function'; import React from 'react'; import { euiStyled } from '../../../../../observability/public'; -import { - ViewSourceConfigurationButton, - ViewSourceConfigurationButtonHrefBase, -} from '../../../components/source_configuration'; +import { ViewSourceConfigurationButton } from '../../../components/source_configuration'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; interface InvalidNodeErrorProps { @@ -59,10 +56,7 @@ export const InvalidNodeError: React.FunctionComponent = - + void; + serverBasePath: string; onManageSpacesClick: () => void; intl: InjectedIntl; capabilities: Capabilities; @@ -184,7 +184,7 @@ class SpacesMenuUI extends Component { diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx index 53d7038cec4d5..4d7af256822c8 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx @@ -23,6 +23,7 @@ export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreSta { const wrapper = shallow( { const wrapper = mountWithIntl( { id={popoutContentId} spaces={this.state.spaces} isLoading={this.state.loading} - onSelectSpace={this.onSelectSpace} + serverBasePath={this.props.serverBasePath} onManageSpacesClick={this.toggleSpaceSelector} capabilities={this.props.capabilities} navigateToApp={this.props.navigateToApp} @@ -175,8 +176,4 @@ export class NavControlPopover extends Component { showSpaceSelector: false, }); }; - - private onSelectSpace = (space: Space) => { - this.props.spacesManager.changeSelectedSpace(space); - }; } diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx index 73dae84c1873b..44215ec538002 100644 --- a/x-pack/plugins/spaces/public/plugin.tsx +++ b/x-pack/plugins/spaces/public/plugin.tsx @@ -43,8 +43,7 @@ export class SpacesPlugin implements Plugin { const space = { @@ -16,21 +17,33 @@ test('it renders without crashing', () => { disabledFeatures: [], }; - shallow(); + shallow(); }); -test('it is clickable', () => { +test('links to the indicated space', () => { const space = { - id: '', + id: 'some-space', name: 'space name', description: 'space description', disabledFeatures: [], }; - const clickHandler = jest.fn(); + const wrapper = mount(); + expect(wrapper.find(EuiCard).props()).toMatchObject({ + href: '/server-base-path/s/some-space/spaces/enter', + }); +}); - const wrapper = mount(); - wrapper.find('button').simulate('click'); +test('links to the default space too', () => { + const space = { + id: 'default', + name: 'default space', + description: 'space description', + disabledFeatures: [], + }; - expect(clickHandler).toHaveBeenCalledTimes(1); + const wrapper = mount(); + expect(wrapper.find(EuiCard).props()).toMatchObject({ + href: '/server-base-path/spaces/enter', + }); }); diff --git a/x-pack/plugins/spaces/public/space_selector/components/space_card.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_card.tsx index f898ba87c60bd..7a056427fb166 100644 --- a/x-pack/plugins/spaces/public/space_selector/components/space_card.tsx +++ b/x-pack/plugins/spaces/public/space_selector/components/space_card.tsx @@ -6,15 +6,16 @@ import { EuiCard } from '@elastic/eui'; import React from 'react'; -import { Space } from '../../../common/model/space'; +import { addSpaceIdToPath, ENTER_SPACE_PATH } from '../../../common'; import { SpaceAvatar } from '../../space_avatar'; +import { Space } from '../..'; interface Props { space: Space; - onClick: () => void; + serverBasePath: string; } export const SpaceCard = (props: Props) => { - const { space, onClick } = props; + const { serverBasePath, space } = props; return ( { icon={renderSpaceAvatar(space)} title={space.name} description={renderSpaceDescription(space)} - onClick={onClick} + href={addSpaceIdToPath(serverBasePath, space.id, ENTER_SPACE_PATH)} /> ); }; diff --git a/x-pack/plugins/spaces/public/space_selector/components/space_cards.test.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_cards.test.tsx index 07b85114e3c8f..8de22bcbe235f 100644 --- a/x-pack/plugins/spaces/public/space_selector/components/space_cards.test.tsx +++ b/x-pack/plugins/spaces/public/space_selector/components/space_cards.test.tsx @@ -16,5 +16,5 @@ test('it renders without crashing', () => { disabledFeatures: [], }; - shallow(); + shallow(); }); diff --git a/x-pack/plugins/spaces/public/space_selector/components/space_cards.tsx b/x-pack/plugins/spaces/public/space_selector/components/space_cards.tsx index 7c4084d36b21d..b480cf524304f 100644 --- a/x-pack/plugins/spaces/public/space_selector/components/space_cards.tsx +++ b/x-pack/plugins/spaces/public/space_selector/components/space_cards.tsx @@ -11,7 +11,7 @@ import { SpaceCard } from './space_card'; interface Props { spaces: Space[]; - onSpaceSelect: (space: Space) => void; + serverBasePath: string; } export class SpaceCards extends Component { @@ -25,15 +25,9 @@ export class SpaceCards extends Component { ); } - public renderSpace = (space: Space) => ( + private renderSpace = (space: Space) => ( - + ); - - public createSpaceClickHandler = (space: Space) => { - return () => { - this.props.onSpaceSelect(space); - }; - }; } diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx index c8173de1661be..112a8a9a0a685 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx @@ -18,7 +18,9 @@ function getSpacesManager(spaces: Space[] = []) { test('it renders without crashing', () => { const spacesManager = getSpacesManager(); - const component = shallowWithIntl(); + const component = shallowWithIntl( + + ); expect(component).toMatchSnapshot(); }); @@ -34,7 +36,9 @@ test('it queries for spaces when loaded', () => { const spacesManager = getSpacesManager(spaces); - shallowWithIntl(); + shallowWithIntl( + + ); return Promise.resolve().then(() => { expect(spacesManager.getSpaces).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx index b63de399b95c9..9289784b9f95f 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx @@ -30,6 +30,7 @@ import { SpacesManager } from '../spaces_manager'; interface Props { spacesManager: SpacesManager; + serverBasePath: string; } interface State { @@ -129,7 +130,7 @@ export class SpaceSelector extends Component { {this.state.loading && } {!this.state.loading && ( - + )} {!this.state.loading && filteredSpaces.length === 0 && ( @@ -179,10 +180,6 @@ export class SpaceSelector extends Component { searchTerm: searchTerm.trim().toLowerCase(), }); }; - - public onSelectSpace = (space: Space) => { - this.props.spacesManager.changeSelectedSpace(space); - }; } export const renderSpaceSelectorApp = (i18nStart: CoreStart['i18n'], el: Element, props: Props) => { diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx index 29ead8d34994d..6fab1767e4b6d 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector_app.tsx @@ -29,7 +29,10 @@ export const spaceSelectorApp = Object.freeze({ getStartServices(), import('./space_selector'), ]); - return renderSpaceSelectorApp(coreStart.i18n, params.element, { spacesManager }); + return renderSpaceSelectorApp(coreStart.i18n, params.element, { + spacesManager, + serverBasePath: coreStart.http.basePath.serverBasePath, + }); }, }); }, diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts index 56879af33916f..6186ac7fd93be 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts @@ -20,7 +20,6 @@ function createSpacesManagerMock() { copySavedObjects: jest.fn().mockResolvedValue(undefined), resolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(undefined), redirectToSpaceSelector: jest.fn().mockResolvedValue(undefined), - changeSelectedSpace: jest.fn(), } as unknown) as jest.Mocked; } diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts index f89bfc32a69e6..508669361c23f 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts @@ -12,14 +12,14 @@ describe('SpacesManager', () => { describe('#constructor', () => { it('attempts to retrieve the active space', () => { const coreStart = coreMock.createStart(); - new SpacesManager('/server-base-path', coreStart.http); + new SpacesManager(coreStart.http); expect(coreStart.http.get).toHaveBeenCalledWith('/internal/spaces/_active_space'); }); it('does not retrieve the active space if on an anonymous path', () => { const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - new SpacesManager('/server-base-path', coreStart.http); + new SpacesManager(coreStart.http); expect(coreStart.http.get).not.toHaveBeenCalled(); }); }); @@ -31,7 +31,7 @@ describe('SpacesManager', () => { id: 'my-space', name: 'my space', }); - const spacesManager = new SpacesManager('/server-base-path', coreStart.http); + const spacesManager = new SpacesManager(coreStart.http); expect(coreStart.http.get).toHaveBeenCalledWith('/internal/spaces/_active_space'); await nextTick(); @@ -47,7 +47,7 @@ describe('SpacesManager', () => { it('throws if on an anonymous path', () => { const coreStart = coreMock.createStart(); coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - const spacesManager = new SpacesManager('/server-base-path', coreStart.http); + const spacesManager = new SpacesManager(coreStart.http); expect(coreStart.http.get).not.toHaveBeenCalled(); expect(() => spacesManager.getActiveSpace()).toThrowErrorMatchingInlineSnapshot( @@ -67,7 +67,7 @@ describe('SpacesManager', () => { name: 'my other space', }); - const spacesManager = new SpacesManager('/server-base-path', coreStart.http); + const spacesManager = new SpacesManager(coreStart.http); expect(coreStart.http.get).toHaveBeenCalledWith('/internal/spaces/_active_space'); await nextTick(); @@ -95,7 +95,7 @@ describe('SpacesManager', () => { name: 'my space', }); - const spacesManager = new SpacesManager('/server-base-path', coreStart.http); + const spacesManager = new SpacesManager(coreStart.http); expect(() => spacesManager.getActiveSpace({ forceRefresh: true }) diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts index e151dcd4f9368..cc3f51b1850de 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -9,16 +9,18 @@ import { HttpSetup } from 'src/core/public'; import { SavedObjectsManagementRecord } from 'src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; -import { ENTER_SPACE_PATH } from '../../common/constants'; import { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types'; -import { addSpaceIdToPath } from '../../common'; export class SpacesManager { private activeSpace$: BehaviorSubject = new BehaviorSubject(null); + private readonly serverBasePath: string; + public readonly onActiveSpaceChange$: Observable; - constructor(private readonly serverBasePath: string, private readonly http: HttpSetup) { + constructor(private readonly http: HttpSetup) { + this.serverBasePath = http.basePath.serverBasePath; + this.onActiveSpaceChange$ = this.activeSpace$ .asObservable() .pipe(skipWhile((v: Space | null) => v == null)) as Observable; @@ -99,10 +101,6 @@ export class SpacesManager { }); } - public async changeSelectedSpace(space: Space) { - window.location.href = addSpaceIdToPath(this.serverBasePath, space.id, ENTER_SPACE_PATH); - } - public redirectToSpaceSelector() { window.location.href = `${this.serverBasePath}/spaces/space_selector`; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index dc97a96d4fcba..4a627d48c3cf0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2872,7 +2872,6 @@ "visualizations.newVisWizard.title": "新規ビジュアライゼーション", "visualizations.newVisWizard.visTypeAliasDescription": "Visualize外でKibanaアプリケーションを開きます。", "visualizations.newVisWizard.visTypeAliasTitle": "Kibanaアプリケーション", - "visualizations.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした", "visDefaultEditor.aggSelect.aggregationLabel": "集約", "visDefaultEditor.aggSelect.helpLinkLabel": "{aggTitle} のヘルプ", "visDefaultEditor.aggSelect.noCompatibleAggsDescription": "インデックスパターン{indexPatternTitle}には集約可能なフィールドが含まれていません。", @@ -12782,12 +12781,9 @@ "xpack.uptime.pingList.statusOptions.downStatusOptionLabel": "ダウン", "xpack.uptime.pingList.statusOptions.upStatusOptionLabel": "アップ", "xpack.uptime.pluginDescription": "アップタイム監視", - "xpack.uptime.snapshot.downCountsMessage": "{down}/{total} 個のモニターがダウンしています", "xpack.uptime.snapshot.noDataDescription": "申し訳ございませんが、ヒストグラムに利用可能なデータがありません", "xpack.uptime.snapshot.noDataTitle": "ヒストグラムデータがありません", - "xpack.uptime.snapshot.noMonitorMessage": "モニターが見つかりません", "xpack.uptime.snapshot.pingsOverTimeTitle": "一定時間のピング", - "xpack.uptime.snapshot.zeroDownMessage": "すべてのモニターが起動しています", "xpack.uptime.snapshotHistogram.description": "{startTime} から {endTime} までの期間のアップタイムステータスを表示する棒グラフです。", "xpack.uptime.snapshotHistogram.downMonitorsId": "ダウンモニター", "xpack.uptime.snapshotHistogram.series.downLabel": "ダウン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2532cdb0c4d07..e0ef4a7a1ebdb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2873,7 +2873,6 @@ "visualizations.newVisWizard.title": "新建可视化", "visualizations.newVisWizard.visTypeAliasDescription": "打开 Visualize 外部的 Kibana 应用程序。", "visualizations.newVisWizard.visTypeAliasTitle": "Kibana 应用程序", - "visualizations.queryGeohashBounds.unableToGetBoundErrorTitle": "无法获取边界", "visDefaultEditor.aggSelect.aggregationLabel": "聚合", "visDefaultEditor.aggSelect.helpLinkLabel": "{aggTitle} 帮助", "visDefaultEditor.aggSelect.noCompatibleAggsDescription": "索引模式“{indexPatternTitle}”不包含任何聚合。", @@ -12782,12 +12781,9 @@ "xpack.uptime.pingList.statusOptions.downStatusOptionLabel": "关闭", "xpack.uptime.pingList.statusOptions.upStatusOptionLabel": "运行", "xpack.uptime.pluginDescription": "运行时间监测", - "xpack.uptime.snapshot.downCountsMessage": "{down}/{total} 个监测已关闭", "xpack.uptime.snapshot.noDataDescription": "抱歉,没有可用于该直方图的数据", "xpack.uptime.snapshot.noDataTitle": "没有可用的直方图数据", - "xpack.uptime.snapshot.noMonitorMessage": "未找到任何监测", "xpack.uptime.snapshot.pingsOverTimeTitle": "时移 Ping 数", - "xpack.uptime.snapshot.zeroDownMessage": "所有监测已启动", "xpack.uptime.snapshotHistogram.description": "显示从 {startTime} 到 {endTime} 的运行时间时移状态的条形图。", "xpack.uptime.snapshotHistogram.downMonitorsId": "已关闭监测", "xpack.uptime.snapshotHistogram.series.downLabel": "关闭", diff --git a/x-pack/plugins/upgrade_assistant/common/config.ts b/x-pack/plugins/upgrade_assistant/common/config.ts new file mode 100644 index 0000000000000..8a13aedd5fdd8 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/common/config.ts @@ -0,0 +1,13 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type Config = TypeOf; diff --git a/x-pack/legacy/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts similarity index 88% rename from x-pack/legacy/plugins/upgrade_assistant/common/types.ts rename to x-pack/plugins/upgrade_assistant/common/types.ts index 0e65506bb584d..a0c12154988a1 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; import { SavedObject, SavedObjectAttributes } from 'src/core/public'; export enum ReindexStep { @@ -114,3 +115,15 @@ export interface UpgradeAssistantTelemetry { export interface UpgradeAssistantTelemetrySavedObjectAttributes { [key: string]: any; } + +export interface EnrichedDeprecationInfo extends DeprecationInfo { + index?: string; + node?: string; + reindex?: boolean; +} + +export interface UpgradeAssistantStatus { + readyForUpgrade: boolean; + cluster: EnrichedDeprecationInfo[]; + indices: EnrichedDeprecationInfo[]; +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/common/version.ts b/x-pack/plugins/upgrade_assistant/common/version.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/common/version.ts rename to x-pack/plugins/upgrade_assistant/common/version.ts diff --git a/x-pack/plugins/upgrade_assistant/kibana.json b/x-pack/plugins/upgrade_assistant/kibana.json new file mode 100644 index 0000000000000..273036a653aeb --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "upgradeAssistant", + "version": "kibana", + "server": true, + "ui": true, + "configPath": ["xpack", "upgrade_assistant"], + "requiredPlugins": ["management", "licensing"], + "optionalPlugins": ["cloud", "usageCollection"] +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/index.scss b/x-pack/plugins/upgrade_assistant/public/application/_index.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/index.scss rename to x-pack/plugins/upgrade_assistant/public/application/_index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx new file mode 100644 index 0000000000000..17eff71f1039b --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { I18nStart } from 'src/core/public'; +import { EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NEXT_MAJOR_VERSION } from '../../common/version'; +import { UpgradeAssistantTabs } from './components/tabs'; +import { AppContextProvider, ContextValue, AppContext } from './app_context'; + +export interface AppDependencies extends ContextValue { + i18n: I18nStart; +} + +export const RootComponent = ({ i18n, ...contexValue }: AppDependencies) => { + return ( + + +
+ + + +

+ +

+
+
+
+ + {({ http }) => } + +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app_context.tsx b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app_context.tsx rename to x-pack/plugins/upgrade_assistant/public/application/app_context.tsx index a48a4efa3bbdf..1ae9dabd69481 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/app_context.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx @@ -9,7 +9,6 @@ import React, { createContext, useContext } from 'react'; export interface ContextValue { http: HttpSetup; isCloudEnabled: boolean; - XSRF: string; } export const AppContext = createContext({} as any); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/_index.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/error_banner.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/error_banner.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/error_banner.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/error_banner.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/latest_minor_banner.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/latest_minor_banner.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx index 864df292fbffe..43d0364425cbb 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/latest_minor_banner.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../common/version'; export const LatestMinorBanner: React.FunctionComponent = () => ( ({ - get: jest.fn(), - create: jest.fn(), -})); - +import { httpServiceMock } from 'src/core/public/mocks'; import { UpgradeAssistantTabs } from './tabs'; import { LoadingState } from './types'; -import axios from 'axios'; import { OverviewTab } from './tabs/overview'; // Used to wait for promises to resolve and renders to finish before reading updates const promisesToResolve = () => new Promise(resolve => setTimeout(resolve, 0)); -const mockHttp = { - basePath: { - prepend: () => 'test', - }, - fetch() {}, -}; +const mockHttp = httpServiceMock.createSetupContract(); describe('UpgradeAssistantTabs', () => { test('renders loading state', async () => { - // @ts-ignore - axios.get.mockReturnValue( + mockHttp.get.mockReturnValue( new Promise(resolve => { /* never resolve */ }) ); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); // Should pass down loading status to child component expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Loading); }); test('successful data fetch', async () => { // @ts-ignore - axios.get.mockResolvedValue({ + mockHttp.get.mockResolvedValue({ data: { cluster: [], indices: [], }, }); const wrapper = mountWithIntl(); - expect(axios.get).toHaveBeenCalled(); + expect(mockHttp.get).toHaveBeenCalled(); await promisesToResolve(); wrapper.update(); // Should pass down success status to child component @@ -59,7 +47,7 @@ describe('UpgradeAssistantTabs', () => { test('network failure', async () => { // @ts-ignore - axios.get.mockRejectedValue(new Error(`oh no!`)); + mockHttp.get.mockRejectedValue(new Error(`oh no!`)); const wrapper = mountWithIntl(); await promisesToResolve(); wrapper.update(); @@ -69,7 +57,7 @@ describe('UpgradeAssistantTabs', () => { it('upgrade error', async () => { // @ts-ignore - axios.get.mockRejectedValue({ response: { status: 426 } }); + mockHttp.get.mockRejectedValue({ response: { status: 426 } }); const wrapper = mountWithIntl(); await promisesToResolve(); wrapper.update(); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx index 0b154fb20404d..43ec5554aaaee 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import axios from 'axios'; import { findIndex, get, set } from 'lodash'; import React from 'react'; @@ -18,7 +17,7 @@ import { import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { HttpSetup } from 'src/core/public'; -import { UpgradeAssistantStatus } from '../../../../server/np_ready/lib/es_migration_apis'; +import { UpgradeAssistantStatus } from '../../../common/types'; import { LatestMinorBanner } from './latest_minor_banner'; import { CheckupTab } from './tabs/checkup'; import { OverviewTab } from './tabs/overview'; @@ -153,12 +152,10 @@ export class UpgradeAssistantTabsUI extends React.Component { private loadData = async () => { try { this.setState({ loadingState: LoadingState.Loading }); - const resp = await axios.get( - this.props.http.basePath.prepend('/api/upgrade_assistant/status') - ); + const resp = await this.props.http.get('/api/upgrade_assistant/status'); this.setState({ loadingState: LoadingState.Success, - checkupData: resp.data, + checkupData: resp, }); } catch (e) { if (get(e, 'response.status') === 426) { diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/__fixtures__/checkup_api_response.json b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/__fixtures__/checkup_api_response.json similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/__fixtures__/checkup_api_response.json rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/__fixtures__/checkup_api_response.json diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/_index.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/_index.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.test.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.test.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx index 7e862a846290b..b047427174e08 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/checkup_tab.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NEXT_MAJOR_VERSION } from '../../../../../../common/version'; +import { NEXT_MAJOR_VERSION } from '../../../../../common/version'; import { LoadingErrorBanner } from '../../error_banner'; import { GroupByOption, diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/constants.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/constants.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/controls.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/controls.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_cell.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_cell.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_cell.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_cell.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_deprecations.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_deprecations.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_deprecations.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_deprecations.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_index.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/_index.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/cell.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx similarity index 94% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/cell.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx index 4bd2f7c4bf62c..879bb695ca60a 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/cell.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx @@ -79,9 +79,7 @@ export const DeprecationCell: FunctionComponent = ({ {reindexIndexName && ( - {({ http, XSRF }) => ( - - )} + {({ http }) => } )} diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/count_summary.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/count_summary.tsx similarity index 93% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/count_summary.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/count_summary.tsx index a0e55dc55c865..7e5172a361a56 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/count_summary.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/count_summary.tsx @@ -8,7 +8,7 @@ import React, { Fragment, FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; export const DeprecationCountSummary: FunctionComponent<{ deprecations: EnrichedDeprecationInfo[]; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx index 28f5f6894b78f..c6309fb57d786 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx @@ -11,7 +11,7 @@ import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { EuiBadge, EuiPagination } from '@elastic/eui'; import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; -import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption, LevelFilterOption } from '../../../types'; import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx index 74f66b6c4fb35..8fa78639c39d3 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/grouped.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx @@ -19,7 +19,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; -import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption, LevelFilterOption } from '../../../types'; import { DeprecationCountSummary } from './count_summary'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/health.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/health.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.test.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.test.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx index 835affce59070..5506528a3ded0 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/index_table.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx @@ -148,9 +148,7 @@ export class IndexDeprecationTableUI extends React.Component< render(indexDep: IndexDeprecationDetails) { return ( - {({ XSRF, http }) => ( - - )} + {({ http }) => } ); }, diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx index 78ded73593464..a1e173737bab0 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption } from '../../../types'; import { DeprecationList } from './list'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx index 15a3d94974dcd..a46bc0d12fad4 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/list.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent } from 'react'; import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; -import { EnrichedDeprecationInfo } from '../../../../../../../server/np_ready/lib/es_migration_apis'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption } from '../../../types'; import { COLOR_MAP, LEVEL_MAP } from '../constants'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_button.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_button.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_button.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_button.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_index.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/_index.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/button.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx index 2a28018a3ae81..30b46e0c15213 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/button.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx @@ -11,14 +11,13 @@ import { Subscription } from 'rxjs'; import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { HttpSetup } from 'src/core/public'; -import { ReindexStatus, UIReindexOption } from '../../../../../../../../common/types'; +import { ReindexStatus, UIReindexOption } from '../../../../../../../common/types'; import { LoadingState } from '../../../../types'; import { ReindexFlyout } from './flyout'; import { ReindexPollingService, ReindexState } from './polling_service'; interface ReindexButtonProps { indexName: string; - xsrf: string; http: HttpSetup; } @@ -154,8 +153,8 @@ export class ReindexButton extends React.Component { diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx index 91e35c0bd7dc0..643dd2e9b6efc 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx @@ -21,7 +21,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ReindexWarning } from '../../../../../../../../../common/types'; +import { ReindexWarning } from '../../../../../../../../common/types'; interface CheckedIds { [id: string]: boolean; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/index.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/index.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts similarity index 63% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts index cb2a0856f0f2e..4228426d62159 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.test.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mockClient } from './polling_service.test.mocks'; - -import { ReindexStatus, ReindexStep } from '../../../../../../../../common/types'; +import { ReindexStatus, ReindexStep } from '../../../../../../../common/types'; import { ReindexPollingService } from './polling_service'; import { httpServiceMock } from 'src/core/public/http/http_service.mock'; +const mockClient = httpServiceMock.createSetupContract(); + describe('ReindexPollingService', () => { beforeEach(() => { mockClient.post.mockReset(); @@ -18,18 +18,11 @@ describe('ReindexPollingService', () => { it('does not poll when reindexOp is null', async () => { mockClient.get.mockResolvedValueOnce({ - status: 200, - data: { - warnings: [], - reindexOp: null, - }, + warnings: [], + reindexOp: null, }); - const service = new ReindexPollingService( - 'myIndex', - 'myXsrf', - httpServiceMock.createSetupContract() - ); + const service = new ReindexPollingService('myIndex', mockClient); service.updateStatus(); await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval @@ -39,22 +32,15 @@ describe('ReindexPollingService', () => { it('does not poll when first check is a 200 and status is failed', async () => { mockClient.get.mockResolvedValue({ - status: 200, - data: { - warnings: [], - reindexOp: { - lastCompletedStep: ReindexStep.created, - status: ReindexStatus.failed, - errorMessage: `Oh no!`, - }, + warnings: [], + reindexOp: { + lastCompletedStep: ReindexStep.created, + status: ReindexStatus.failed, + errorMessage: `Oh no!`, }, }); - const service = new ReindexPollingService( - 'myIndex', - 'myXsrf', - httpServiceMock.createSetupContract() - ); + const service = new ReindexPollingService('myIndex', mockClient); service.updateStatus(); await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval @@ -65,21 +51,14 @@ describe('ReindexPollingService', () => { it('begins to poll when first check is a 200 and status is inProgress', async () => { mockClient.get.mockResolvedValue({ - status: 200, - data: { - warnings: [], - reindexOp: { - lastCompletedStep: ReindexStep.created, - status: ReindexStatus.inProgress, - }, + warnings: [], + reindexOp: { + lastCompletedStep: ReindexStep.created, + status: ReindexStatus.inProgress, }, }); - const service = new ReindexPollingService( - 'myIndex', - 'myXsrf', - httpServiceMock.createSetupContract() - ); + const service = new ReindexPollingService('myIndex', mockClient); service.updateStatus(); await new Promise(resolve => setTimeout(resolve, 1200)); // wait for poll interval @@ -89,11 +68,7 @@ describe('ReindexPollingService', () => { describe('startReindex', () => { it('posts to endpoint', async () => { - const service = new ReindexPollingService( - 'myIndex', - 'myXsrf', - httpServiceMock.createSetupContract() - ); + const service = new ReindexPollingService('myIndex', mockClient); await service.startReindex(); expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex'); @@ -102,11 +77,7 @@ describe('ReindexPollingService', () => { describe('cancelReindex', () => { it('posts to cancel endpoint', async () => { - const service = new ReindexPollingService( - 'myIndex', - 'myXsrf', - httpServiceMock.createSetupContract() - ); + const service = new ReindexPollingService('myIndex', mockClient); await service.cancelReindex(); expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex/cancel'); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.ts b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.ts similarity index 82% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.ts index 879fafe610982..6fe6a85905706 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/deprecations/reindex/polling_service.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/polling_service.ts @@ -3,8 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import axios, { AxiosInstance } from 'axios'; - import { BehaviorSubject } from 'rxjs'; import { HttpSetup } from 'src/core/public'; @@ -14,7 +12,7 @@ import { ReindexStatus, ReindexStep, ReindexWarning, -} from '../../../../../../../../common/types'; +} from '../../../../../../../common/types'; import { LoadingState } from '../../../../types'; const POLL_INTERVAL = 1000; @@ -45,24 +43,13 @@ interface StatusResponse { export class ReindexPollingService { public status$: BehaviorSubject; private pollTimeout?: NodeJS.Timeout; - private APIClient: AxiosInstance; - constructor(private indexName: string, private xsrf: string, private http: HttpSetup) { + constructor(private indexName: string, private http: HttpSetup) { this.status$ = new BehaviorSubject({ loadingState: LoadingState.Loading, errorMessage: null, reindexTaskPercComplete: null, }); - - this.APIClient = axios.create({ - headers: { - Accept: 'application/json', - credentials: 'same-origin', - 'Content-Type': 'application/json', - 'kbn-version': this.xsrf, - 'kbn-xsrf': this.xsrf, - }, - }); } public updateStatus = async () => { @@ -70,8 +57,8 @@ export class ReindexPollingService { this.stopPolling(); try { - const { data } = await this.APIClient.get( - this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}`) + const data = await this.http.get( + `/api/upgrade_assistant/reindex/${this.indexName}` ); this.updateWithResponse(data); @@ -107,8 +94,8 @@ export class ReindexPollingService { errorMessage: null, cancelLoadingState: undefined, }); - const { data } = await this.APIClient.post( - this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}`) + const data = await this.http.post( + `/api/upgrade_assistant/reindex/${this.indexName}` ); this.updateWithResponse({ reindexOp: data }); @@ -125,9 +112,7 @@ export class ReindexPollingService { cancelLoadingState: LoadingState.Loading, }); - await this.APIClient.post( - this.http.basePath.prepend(`/api/upgrade_assistant/reindex/${this.indexName}/cancel`) - ); + await this.http.post(`/api/upgrade_assistant/reindex/${this.indexName}/cancel`); } catch (e) { this.status$.next({ ...this.status$.value, diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/filter_bar.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.test.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/group_by_bar.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/group_by_bar.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/index.tsx similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/index.tsx diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_index.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_index.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_index.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_steps.scss b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_steps.scss similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/_steps.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/_steps.scss diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/deprecation_logging_toggle.tsx similarity index 86% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/deprecation_logging_toggle.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/deprecation_logging_toggle.tsx index db37bc58904ec..0e6c79dc47b53 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/deprecation_logging_toggle.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/deprecation_logging_toggle.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import axios from 'axios'; import React from 'react'; import { EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; @@ -15,7 +14,6 @@ import { HttpSetup } from 'src/core/public'; import { LoadingState } from '../../types'; interface DeprecationLoggingTabProps extends ReactIntl.InjectedIntlProps { - xsrf: string; http: HttpSetup; } @@ -88,12 +86,10 @@ export class DeprecationLoggingToggleUI extends React.Component< private loadData = async () => { try { this.setState({ loadingState: LoadingState.Loading }); - const resp = await axios.get( - this.props.http.basePath.prepend('/api/upgrade_assistant/deprecation_logging') - ); + const resp = await this.props.http.get('/api/upgrade_assistant/deprecation_logging'); this.setState({ loadingState: LoadingState.Success, - loggingEnabled: resp.data.isEnabled, + loggingEnabled: resp.isEnabled, }); } catch (e) { this.setState({ loadingState: LoadingState.Error }); @@ -102,26 +98,19 @@ export class DeprecationLoggingToggleUI extends React.Component< private toggleLogging = async () => { try { - const { http, xsrf } = this.props; // Optimistically toggle the UI const newEnabled = !this.state.loggingEnabled; this.setState({ loadingState: LoadingState.Loading, loggingEnabled: newEnabled }); - const resp = await axios.put( - http.basePath.prepend('/api/upgrade_assistant/deprecation_logging'), - { + const resp = await this.props.http.put('/api/upgrade_assistant/deprecation_logging', { + body: JSON.stringify({ isEnabled: newEnabled, - }, - { - headers: { - 'kbn-xsrf': xsrf, - }, - } - ); + }), + }); this.setState({ loadingState: LoadingState.Success, - loggingEnabled: resp.data.isEnabled, + loggingEnabled: resp.isEnabled, }); } catch (e) { this.setState({ loadingState: LoadingState.Error }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/index.tsx similarity index 96% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/index.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/index.tsx index 284265bb31f14..aede377fa8d45 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/index.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/index.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NEXT_MAJOR_VERSION } from '../../../../../../common/version'; +import { NEXT_MAJOR_VERSION } from '../../../../../common/version'; import { LoadingErrorBanner } from '../../error_banner'; import { LoadingState, UpgradeAssistantTabProps } from '../../types'; import { Steps } from './steps'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/steps.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/steps.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx index ccba51c73c136..85d275b080e13 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/overview/steps.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx @@ -19,7 +19,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../../../common/version'; +import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../../common/version'; import { UpgradeAssistantTabProps } from '../../types'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; import { useAppContext } from '../../../app_context'; @@ -104,7 +104,7 @@ export const StepsUI: FunctionComponent - + ), diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/types.ts b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts similarity index 89% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/types.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/types.ts index 2d9a373f20b7e..86d1486543596 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/types.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts @@ -6,10 +6,7 @@ import React from 'react'; -import { - EnrichedDeprecationInfo, - UpgradeAssistantStatus, -} from '../../../../server/np_ready/lib/es_migration_apis'; +import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../../common/types'; export interface UpgradeAssistantTabProps { alertBanner?: React.ReactNode; diff --git a/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx b/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx new file mode 100644 index 0000000000000..97120cfc3333a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { AppDependencies, RootComponent } from './app'; + +interface BootDependencies extends AppDependencies { + element: HTMLElement; +} + +export const renderApp = (deps: BootDependencies) => { + const { element, ...appDependencies } = deps; + render(, element); + return () => { + unmountComponentAtNode(element); + }; +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/utils.test.ts b/x-pack/plugins/upgrade_assistant/public/application/utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/utils.test.ts rename to x-pack/plugins/upgrade_assistant/public/application/utils.test.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/utils.ts b/x-pack/plugins/upgrade_assistant/public/application/utils.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/utils.ts rename to x-pack/plugins/upgrade_assistant/public/application/utils.ts diff --git a/x-pack/plugins/upgrade_assistant/public/index.scss b/x-pack/plugins/upgrade_assistant/public/index.scss new file mode 100644 index 0000000000000..9bd47b6473372 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/index.scss @@ -0,0 +1 @@ +@import './application/index'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/index.ts b/x-pack/plugins/upgrade_assistant/public/index.ts similarity index 62% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/index.ts rename to x-pack/plugins/upgrade_assistant/public/index.ts index cf1b78e1e3920..2b1860167ef5d 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/index.ts @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; -import { UpgradeAssistantServerPlugin } from './plugin'; +import './index.scss'; +import { PluginInitializerContext } from 'src/core/public'; +import { UpgradeAssistantUIPlugin } from './plugin'; export const plugin = (ctx: PluginInitializerContext) => { - return new UpgradeAssistantServerPlugin(); + return new UpgradeAssistantUIPlugin(ctx); }; diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts new file mode 100644 index 0000000000000..614221272dd5c --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -0,0 +1,48 @@ +/* + * 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 { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public'; + +import { CloudSetup } from '../../cloud/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; + +import { NEXT_MAJOR_VERSION } from '../common/version'; +import { Config } from '../common/config'; + +import { renderApp } from './application/render_app'; + +interface Dependencies { + cloud: CloudSetup; + management: ManagementSetup; +} + +export class UpgradeAssistantUIPlugin implements Plugin { + constructor(private ctx: PluginInitializerContext) {} + setup({ http, getStartServices }: CoreSetup, { cloud, management }: Dependencies) { + const { enabled } = this.ctx.config.get(); + if (!enabled) { + return; + } + const appRegistrar = management.sections.getSection('elasticsearch')!; + const isCloudEnabled = Boolean(cloud?.isCloudEnabled); + + appRegistrar.registerApp({ + id: 'upgrade_assistant', + title: i18n.translate('xpack.upgradeAssistant.appTitle', { + defaultMessage: '{version} Upgrade Assistant', + values: { version: `${NEXT_MAJOR_VERSION}.0` }, + }), + order: 1000, + async mount({ element }) { + const [{ i18n: i18nDep }] = await getStartServices(); + return renderApp({ element, isCloudEnabled, http, i18n: i18nDep }); + }, + }); + } + + start() {} + stop() {} +} diff --git a/x-pack/plugins/upgrade_assistant/server/index.ts b/x-pack/plugins/upgrade_assistant/server/index.ts new file mode 100644 index 0000000000000..cab7eb613f74c --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; +import { UpgradeAssistantServerPlugin } from './plugin'; +import { configSchema } from '../common/config'; + +export const plugin = (ctx: PluginInitializerContext) => { + return new UpgradeAssistantServerPlugin(ctx); +}; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + enabled: true, + }, +}; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__fixtures__/fake_deprecations.json b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__fixtures__/fake_deprecations.json rename to x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__mocks__/es_version_precheck.ts b/x-pack/plugins/upgrade_assistant/server/lib/__mocks__/es_version_precheck.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__mocks__/es_version_precheck.ts rename to x-pack/plugins/upgrade_assistant/server/lib/__mocks__/es_version_precheck.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__snapshots__/es_migration_apis.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/__snapshots__/es_migration_apis.test.ts.snap rename to x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts similarity index 75% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts index 317e2a7554e03..862f64e232370 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { getDeprecationLoggingStatus, isDeprecationLoggingEnabled, @@ -12,9 +12,9 @@ import { describe('getDeprecationLoggingStatus', () => { it('calls cluster.getSettings', async () => { - const callWithRequest = jest.fn(); - await getDeprecationLoggingStatus(callWithRequest, {} as any); - expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.getSettings', { + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); + await getDeprecationLoggingStatus(dataClient); + expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.getSettings', { includeDefaults: true, }); }); @@ -23,9 +23,9 @@ describe('getDeprecationLoggingStatus', () => { describe('setDeprecationLogging', () => { describe('isEnabled = true', () => { it('calls cluster.putSettings with logger.deprecation = WARN', async () => { - const callWithRequest = jest.fn(); - await setDeprecationLogging(callWithRequest, {} as any, true); - expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.putSettings', { + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); + await setDeprecationLogging(dataClient, true); + expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.putSettings', { body: { transient: { 'logger.deprecation': 'WARN' } }, }); }); @@ -33,9 +33,9 @@ describe('setDeprecationLogging', () => { describe('isEnabled = false', () => { it('calls cluster.putSettings with logger.deprecation = ERROR', async () => { - const callWithRequest = jest.fn(); - await setDeprecationLogging(callWithRequest, {} as any, false); - expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.putSettings', { + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); + await setDeprecationLogging(dataClient, false); + expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.putSettings', { body: { transient: { 'logger.deprecation': 'ERROR' } }, }); }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts similarity index 75% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts index 199d389408442..8f25533c0afb1 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_deprecation_logging_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts @@ -4,19 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; - -import { CallClusterWithRequest } from 'src/legacy/core_plugins/elasticsearch'; -import { RequestShim } from '../types'; +import { IScopedClusterClient } from 'src/core/server'; interface DeprecationLoggingStatus { isEnabled: boolean; } export async function getDeprecationLoggingStatus( - callWithRequest: CallClusterWithRequest, - req: RequestShim + dataClient: IScopedClusterClient ): Promise { - const response = await callWithRequest(req, 'cluster.getSettings', { + const response = await dataClient.callAsCurrentUser('cluster.getSettings', { includeDefaults: true, }); @@ -26,11 +23,10 @@ export async function getDeprecationLoggingStatus( } export async function setDeprecationLogging( - callWithRequest: CallClusterWithRequest, - req: RequestShim, + dataClient: IScopedClusterClient, isEnabled: boolean ): Promise { - const response = await callWithRequest(req, 'cluster.putSettings', { + const response = await dataClient.callAsCurrentUser('cluster.putSettings', { body: { transient: { 'logger.deprecation': isEnabled ? 'WARN' : 'ERROR', diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts similarity index 73% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts index a1d7049e4171f..4ab4227ba3e91 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts @@ -5,6 +5,7 @@ */ import _ from 'lodash'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { getUpgradeAssistantStatus } from './es_migration_apis'; import { DeprecationAPIResponse } from 'src/legacy/core_plugins/elasticsearch'; @@ -13,7 +14,8 @@ import fakeDeprecations from './__fixtures__/fake_deprecations.json'; describe('getUpgradeAssistantStatus', () => { let deprecationsResponse: DeprecationAPIResponse; - const callWithRequest = jest.fn().mockImplementation(async (req, api, { path }) => { + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); + (dataClient.callAsCurrentUser as jest.Mock).mockImplementation(async (api, { path }) => { if (path === '/_migration/deprecations') { return deprecationsResponse; } else if (api === 'indices.getMapping') { @@ -28,15 +30,15 @@ describe('getUpgradeAssistantStatus', () => { }); it('calls /_migration/deprecations', async () => { - await getUpgradeAssistantStatus(callWithRequest, {} as any, false); - expect(callWithRequest).toHaveBeenCalledWith({}, 'transport.request', { + await getUpgradeAssistantStatus(dataClient, false); + expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('transport.request', { path: '/_migration/deprecations', method: 'GET', }); }); it('returns the correct shape of data', async () => { - const resp = await getUpgradeAssistantStatus(callWithRequest, {} as any, false); + const resp = await getUpgradeAssistantStatus(dataClient, false); expect(resp).toMatchSnapshot(); }); @@ -48,9 +50,10 @@ describe('getUpgradeAssistantStatus', () => { index_settings: {}, }; - await expect( - getUpgradeAssistantStatus(callWithRequest, {} as any, false) - ).resolves.toHaveProperty('readyForUpgrade', false); + await expect(getUpgradeAssistantStatus(dataClient, false)).resolves.toHaveProperty( + 'readyForUpgrade', + false + ); }); it('returns readyForUpgrade === true when no critical issues found', async () => { @@ -61,9 +64,10 @@ describe('getUpgradeAssistantStatus', () => { index_settings: {}, }; - await expect( - getUpgradeAssistantStatus(callWithRequest, {} as any, false) - ).resolves.toHaveProperty('readyForUpgrade', true); + await expect(getUpgradeAssistantStatus(dataClient, false)).resolves.toHaveProperty( + 'readyForUpgrade', + true + ); }); it('filters out security realm deprecation on Cloud', async () => { @@ -80,7 +84,7 @@ describe('getUpgradeAssistantStatus', () => { index_settings: {}, }; - const result = await getUpgradeAssistantStatus(callWithRequest, {} as any, true); + const result = await getUpgradeAssistantStatus(dataClient, true); expect(result).toHaveProperty('readyForUpgrade', true); expect(result).toHaveProperty('cluster', []); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts similarity index 75% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts index b52c4c374266f..68f21c1fd93b5 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_migration_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts @@ -4,32 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - CallClusterWithRequest, - DeprecationAPIResponse, - DeprecationInfo, -} from 'src/legacy/core_plugins/elasticsearch'; - -import { RequestShim } from '../types'; - -export interface EnrichedDeprecationInfo extends DeprecationInfo { - index?: string; - node?: string; - reindex?: boolean; -} - -export interface UpgradeAssistantStatus { - readyForUpgrade: boolean; - cluster: EnrichedDeprecationInfo[]; - indices: EnrichedDeprecationInfo[]; -} +import { IScopedClusterClient } from 'src/core/server'; +import { DeprecationAPIResponse } from 'src/legacy/core_plugins/elasticsearch'; +import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../common/types'; export async function getUpgradeAssistantStatus( - callWithRequest: CallClusterWithRequest, - req: RequestShim, + dataClient: IScopedClusterClient, isCloudEnabled: boolean ): Promise { - const deprecations = await callWithRequest(req, 'transport.request', { + const deprecations = await dataClient.callAsCurrentUser('transport.request', { path: '/_migration/deprecations', method: 'GET', }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts index bbabe557df4d4..51cb776ef4e0b 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts @@ -6,7 +6,7 @@ import { SemVer } from 'semver'; import { IScopedClusterClient, kibanaResponseFactory } from 'src/core/server'; -import { CURRENT_VERSION } from '../../../common/version'; +import { CURRENT_VERSION } from '../../common/version'; import { esVersionCheck, getAllNodeVersions, diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts similarity index 98% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts index 2fb3effe43793..e7636eea66479 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/es_version_precheck.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts @@ -13,7 +13,7 @@ import { RequestHandler, RequestHandlerContext, } from 'src/core/server'; -import { CURRENT_VERSION } from '../../../common/version'; +import { CURRENT_VERSION } from '../../common/version'; /** * Returns an array of all the unique Elasticsearch Node Versions in the Elasticsearch cluster. diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts similarity index 95% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts index 06fa755472238..ce892df0de946 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ReindexSavedObject } from '../../../../common/types'; +import { ReindexSavedObject } from '../../../common/types'; import { Credential, credentialStoreFactory } from './credential_store'; describe('credentialStore', () => { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.ts similarity index 91% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.ts index a051d16b5779f..0958559910be6 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/credential_store.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.ts @@ -5,12 +5,11 @@ */ import { createHash } from 'crypto'; -import { Request } from 'hapi'; import stringify from 'json-stable-stringify'; -import { ReindexSavedObject } from '../../../../common/types'; +import { ReindexSavedObject } from '../../../common/types'; -export type Credential = Request['headers']; +export type Credential = Record; /** * An in-memory cache for user credentials to be used for reindexing operations. When looking up diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/index.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.test.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.test.ts index 7b346cc87edf6..9ec06b72f02e2 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; import { generateNewIndexName, getReindexWarnings, diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts index 0b95bc628fbb4..f6dc471d0945d 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/index_settings.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts @@ -5,8 +5,8 @@ */ import { flow, omit } from 'lodash'; -import { ReindexWarning } from '../../../../common/types'; -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; +import { ReindexWarning } from '../../../common/types'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; import { FlatSettings } from './types'; export interface ParsedIndexName { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts similarity index 99% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts index 3fb855958a5d0..4569fdfa33a83 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts @@ -13,8 +13,8 @@ import { ReindexSavedObject, ReindexStatus, ReindexStep, -} from '../../../../common/types'; -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; +} from '../../../common/types'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; import { LOCK_WINDOW, ReindexActions, reindexActionsFactory } from './reindex_actions'; describe('ReindexActions', () => { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts similarity index 97% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts index 6683f80c8e779..2ae340f12d80c 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_actions.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts @@ -6,8 +6,7 @@ import moment from 'moment'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { SavedObjectsFindResponse, SavedObjectsClientContract } from 'kibana/server'; +import { SavedObjectsFindResponse, SavedObjectsClientContract, APICaller } from 'src/core/server'; import { IndexGroup, REINDEX_OP_TYPE, @@ -15,7 +14,7 @@ import { ReindexSavedObject, ReindexStatus, ReindexStep, -} from '../../../../common/types'; +} from '../../../common/types'; import { generateNewIndexName } from './index_settings'; import { FlatSettings } from './types'; @@ -111,7 +110,7 @@ export interface ReindexActions { export const reindexActionsFactory = ( client: SavedObjectsClientContract, - callCluster: CallCluster + callAsUser: APICaller ): ReindexActions => { // ----- Internal functions const isLocked = (reindexOp: ReindexSavedObject) => { @@ -230,7 +229,7 @@ export const reindexActionsFactory = ( }, async getFlatSettings(indexName: string) { - const flatSettings = (await callCluster('transport.request', { + const flatSettings = (await callAsUser('transport.request', { path: `/${encodeURIComponent(indexName)}?flat_settings=true`, })) as { [indexName: string]: FlatSettings }; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts similarity index 95% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts index 9cd41c8cbe826..6c3b2c869dc7f 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts @@ -4,14 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { BehaviorSubject } from 'rxjs'; +import { Logger } from 'src/core/server'; +import { loggingServiceMock } from 'src/core/server/mocks'; + import { IndexGroup, ReindexOperation, ReindexSavedObject, ReindexStatus, ReindexStep, -} from '../../../../common/types'; -import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../../common/version'; +} from '../../../common/types'; +import { CURRENT_MAJOR_VERSION, PREV_MAJOR_VERSION } from '../../../common/version'; +import { licensingMock } from '../../../../licensing/server/mocks'; +import { LicensingPluginSetup } from '../../../../licensing/server'; + import { isMlIndex, isWatcherIndex, @@ -22,9 +29,9 @@ import { describe('reindexService', () => { let actions: jest.Mocked; let callCluster: jest.Mock; - let log: jest.Mock; - let xpackInfo: { feature: jest.Mocked }; + let log: Logger; let service: ReindexService; + let licensingPluginSetup: LicensingPluginSetup; const updateMockImpl = (reindexOp: ReindexSavedObject, attrs: Partial = {}) => Promise.resolve({ @@ -50,32 +57,24 @@ describe('reindexService', () => { runWhileIndexGroupLocked: jest.fn(async (group: string, f: any) => f({ attributes: {} })), }; callCluster = jest.fn(); - log = jest.fn(); - xpackInfo = { - feature: jest.fn(() => ({ - isAvailable() { - return true; - }, - isEnabled() { - return true; - }, - })), - }; - - service = reindexServiceFactory(callCluster as any, xpackInfo as any, actions, log); + log = loggingServiceMock.create().get(); + licensingPluginSetup = licensingMock.createSetup(); + licensingPluginSetup.license$ = new BehaviorSubject( + licensingMock.createLicense({ + features: { security: { isAvailable: true, isEnabled: true } }, + }) + ); + + service = reindexServiceFactory(callCluster as any, actions, log, licensingPluginSetup); }); describe('hasRequiredPrivileges', () => { it('returns true if security is disabled', async () => { - xpackInfo.feature.mockReturnValueOnce({ - isAvailable() { - return true; - }, - isEnabled() { - return false; - }, - }); - + licensingPluginSetup.license$ = new BehaviorSubject( + licensingMock.createLicense({ + features: { security: { isAvailable: true, isEnabled: false } }, + }) + ); const hasRequired = await service.hasRequiredPrivileges('anIndex'); expect(hasRequired).toBe(true); }); @@ -584,7 +583,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=true', method: 'POST', @@ -599,7 +598,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=true', method: 'POST', @@ -623,7 +622,7 @@ describe('reindexService', () => { expect( updatedOp.attributes.errorMessage!.includes('Could not stop ML jobs') ).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=true', method: 'POST', @@ -645,7 +644,7 @@ describe('reindexService', () => { expect( updatedOp.attributes.errorMessage!.includes('Some nodes are not on minimum version') ).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); // Should not have called ML endpoint at all expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=true', @@ -698,7 +697,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_watcher/_stop', method: 'POST', @@ -713,7 +712,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_watcher/_stop', method: 'POST', @@ -735,7 +734,7 @@ describe('reindexService', () => { expect( updatedOp.attributes.errorMessage!.includes('Could not stop Watcher') ).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).toHaveBeenCalledWith('transport.request', { path: '/_watcher/_stop', method: 'POST', @@ -771,7 +770,7 @@ describe('reindexService', () => { ); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); it('fails if setting updates fail', async () => { @@ -782,7 +781,7 @@ describe('reindexService', () => { ); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); }); @@ -817,7 +816,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.readonly); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); it('fails if create index fails', async () => { @@ -829,7 +828,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.readonly); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); // Original index should have been set back to allow reads. expect(callCluster).toHaveBeenCalledWith('indices.putSettings', { @@ -874,7 +873,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.newIndexCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); }); @@ -931,7 +930,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexStarted); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); }); @@ -1014,7 +1013,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexCompleted); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); it('fails if switching aliases fails', async () => { @@ -1023,7 +1022,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexCompleted); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage).not.toBeNull(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); }); }); @@ -1092,7 +1091,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=false', method: 'POST', @@ -1108,7 +1107,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=false', method: 'POST', @@ -1129,7 +1128,7 @@ describe('reindexService', () => { expect( updatedOp.attributes.errorMessage!.includes('Could not resume ML jobs') ).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).toHaveBeenCalledWith('transport.request', { path: '/_ml/set_upgrade_mode?enabled=false', method: 'POST', @@ -1196,7 +1195,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_watcher/_start', method: 'POST', @@ -1212,7 +1211,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).not.toHaveBeenCalledWith('transport.request', { path: '/_watcher/_start', method: 'POST', @@ -1233,7 +1232,7 @@ describe('reindexService', () => { expect( updatedOp.attributes.errorMessage!.includes('Could not start Watcher') ).toBeTruthy(); - expect(log).toHaveBeenCalledWith(['upgrade_assistant', 'error'], expect.any(String)); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); expect(callCluster).toHaveBeenCalledWith('transport.request', { path: '/_watcher/_start', method: 'POST', diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts similarity index 90% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts index 0e6095f98b6ff..8f1df5b34372b 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/reindex_service.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts @@ -5,17 +5,16 @@ */ import Boom from 'boom'; +import { APICaller, Logger } from 'src/core/server'; +import { first } from 'rxjs/operators'; -import { Server } from 'hapi'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; import { IndexGroup, ReindexSavedObject, ReindexStatus, ReindexStep, ReindexWarning, -} from '../../../../common/types'; +} from '../../../common/types'; import { generateNewIndexName, getReindexWarnings, @@ -23,6 +22,7 @@ import { transformFlatSettings, } from './index_settings'; import { ReindexActions } from './reindex_actions'; +import { LicensingPluginSetup } from '../../../../licensing/server'; const VERSION_REGEX = new RegExp(/^([1-9]+)\.([0-9]+)\.([0-9]+)/); const ML_INDICES = ['.ml-state', '.ml-anomalies', '.ml-config']; @@ -97,10 +97,10 @@ export interface ReindexService { } export const reindexServiceFactory = ( - callCluster: CallCluster, - xpackInfo: XPackInfo, + callAsUser: APICaller, actions: ReindexActions, - log: Server['log'] + log: Logger, + licensing: LicensingPluginSetup ): ReindexService => { // ------ Utility functions @@ -114,7 +114,7 @@ export const reindexServiceFactory = ( await actions.runWhileIndexGroupLocked(IndexGroup.ml, async mlDoc => { await validateNodesMinimumVersion(6, 7); - const res = await callCluster('transport.request', { + const res = await callAsUser('transport.request', { path: '/_ml/set_upgrade_mode?enabled=true', method: 'POST', }); @@ -134,7 +134,7 @@ export const reindexServiceFactory = ( await actions.decrementIndexGroupReindexes(IndexGroup.ml); await actions.runWhileIndexGroupLocked(IndexGroup.ml, async mlDoc => { if (mlDoc.attributes.runningReindexCount === 0) { - const res = await callCluster('transport.request', { + const res = await callAsUser('transport.request', { path: '/_ml/set_upgrade_mode?enabled=false', method: 'POST', }); @@ -154,7 +154,7 @@ export const reindexServiceFactory = ( const stopWatcher = async () => { await actions.incrementIndexGroupReindexes(IndexGroup.watcher); await actions.runWhileIndexGroupLocked(IndexGroup.watcher, async watcherDoc => { - const { acknowledged } = await callCluster('transport.request', { + const { acknowledged } = await callAsUser('transport.request', { path: '/_watcher/_stop', method: 'POST', }); @@ -174,7 +174,7 @@ export const reindexServiceFactory = ( await actions.decrementIndexGroupReindexes(IndexGroup.watcher); await actions.runWhileIndexGroupLocked(IndexGroup.watcher, async watcherDoc => { if (watcherDoc.attributes.runningReindexCount === 0) { - const { acknowledged } = await callCluster('transport.request', { + const { acknowledged } = await callAsUser('transport.request', { path: '/_watcher/_start', method: 'POST', }); @@ -191,14 +191,14 @@ export const reindexServiceFactory = ( const cleanupChanges = async (reindexOp: ReindexSavedObject) => { // Cancel reindex task if it was started but not completed if (reindexOp.attributes.lastCompletedStep === ReindexStep.reindexStarted) { - await callCluster('tasks.cancel', { + await callAsUser('tasks.cancel', { taskId: reindexOp.attributes.reindexTaskId, }).catch(e => undefined); // Ignore any exceptions trying to cancel (it may have already completed). } // Set index back to writable if we ever got past this point. if (reindexOp.attributes.lastCompletedStep >= ReindexStep.readonly) { - await callCluster('indices.putSettings', { + await callAsUser('indices.putSettings', { index: reindexOp.attributes.indexName, body: { 'index.blocks.write': false }, }); @@ -208,7 +208,7 @@ export const reindexServiceFactory = ( reindexOp.attributes.lastCompletedStep >= ReindexStep.newIndexCreated && reindexOp.attributes.lastCompletedStep < ReindexStep.aliasCreated ) { - await callCluster('indices.delete', { index: reindexOp.attributes.newIndexName }); + await callAsUser('indices.delete', { index: reindexOp.attributes.newIndexName }); } // Resume consumers if we ever got past this point. @@ -222,7 +222,7 @@ export const reindexServiceFactory = ( // ------ Functions used to process the state machine const validateNodesMinimumVersion = async (minMajor: number, minMinor: number) => { - const nodesResponse = await callCluster('transport.request', { + const nodesResponse = await callAsUser('transport.request', { path: '/_nodes', method: 'GET', }); @@ -263,7 +263,7 @@ export const reindexServiceFactory = ( */ const setReadonly = async (reindexOp: ReindexSavedObject) => { const { indexName } = reindexOp.attributes; - const putReadonly = await callCluster('indices.putSettings', { + const putReadonly = await callAsUser('indices.putSettings', { index: indexName, body: { 'index.blocks.write': true }, }); @@ -289,7 +289,7 @@ export const reindexServiceFactory = ( const { settings, mappings } = transformFlatSettings(flatSettings); - const createIndex = await callCluster('indices.create', { + const createIndex = await callAsUser('indices.create', { index: newIndexName, body: { settings, @@ -313,7 +313,7 @@ export const reindexServiceFactory = ( const startReindexing = async (reindexOp: ReindexSavedObject) => { const { indexName } = reindexOp.attributes; - const startReindex = (await callCluster('reindex', { + const startReindex = (await callAsUser('reindex', { refresh: true, waitForCompletion: false, body: { @@ -337,7 +337,7 @@ export const reindexServiceFactory = ( const taskId = reindexOp.attributes.reindexTaskId; // Check reindexing task progress - const taskResponse = await callCluster('tasks.get', { + const taskResponse = await callAsUser('tasks.get', { taskId, waitForCompletion: false, }); @@ -358,7 +358,7 @@ export const reindexServiceFactory = ( reindexOp = await cleanupChanges(reindexOp); } else { // Check that it reindexed all documents - const { count } = await callCluster('count', { index: reindexOp.attributes.indexName }); + const { count } = await callAsUser('count', { index: reindexOp.attributes.indexName }); if (taskResponse.task.status.created < count) { // Include the entire task result in the error message. This should be guaranteed @@ -374,7 +374,7 @@ export const reindexServiceFactory = ( } // Delete the task from ES .tasks index - const deleteTaskResp = await callCluster('delete', { + const deleteTaskResp = await callAsUser('delete', { index: '.tasks', id: taskId, }); @@ -394,7 +394,7 @@ export const reindexServiceFactory = ( const { indexName, newIndexName } = reindexOp.attributes; const existingAliases = ( - await callCluster('indices.getAlias', { + await callAsUser('indices.getAlias', { index: indexName, }) )[indexName].aliases; @@ -403,7 +403,7 @@ export const reindexServiceFactory = ( add: { index: newIndexName, alias: aliasName, ...existingAliases[aliasName] }, })); - const aliasResponse = await callCluster('indices.updateAliases', { + const aliasResponse = await callAsUser('indices.updateAliases', { body: { actions: [ { add: { index: newIndexName, alias: indexName } }, @@ -443,9 +443,18 @@ export const reindexServiceFactory = ( return { async hasRequiredPrivileges(indexName: string) { + /** + * To avoid a circular dependency on Security we use a work around + * here to detect whether Security is available and enabled + * (i.e., via the licensing plugin). This enables Security to use + * functionality exposed through Upgrade Assistant. + */ + const license = await licensing.license$.pipe(first()).toPromise(); + + const securityFeature = license.getFeature('security'); + // If security is disabled or unavailable, return true. - const security = xpackInfo.feature('security'); - if (!security.isAvailable() || !security.isEnabled()) { + if (!securityFeature || !(securityFeature.isAvailable && securityFeature.isEnabled)) { return true; } @@ -482,7 +491,7 @@ export const reindexServiceFactory = ( body.cluster = [...body.cluster, 'manage_watcher']; } - const resp = await callCluster('transport.request', { + const resp = await callAsUser('transport.request', { path: '/_security/user/_has_privileges', method: 'POST', body, @@ -509,7 +518,7 @@ export const reindexServiceFactory = ( }, async createReindexOperation(indexName: string) { - const indexExists = await callCluster('indices.exists', { index: indexName }); + const indexExists = await callAsUser('indices.exists', { index: indexName }); if (!indexExists) { throw Boom.notFound(`Index ${indexName} does not exist in this cluster.`); } @@ -579,10 +588,7 @@ export const reindexServiceFactory = ( break; } } catch (e) { - log( - ['upgrade_assistant', 'error'], - `Reindexing step failed: ${e instanceof Error ? e.stack : e.toString()}` - ); + log.error(`Reindexing step failed: ${e instanceof Error ? e.stack : e.toString()}`); // Trap the exception and add the message to the object so the UI can display it. lockedReindexOp = await actions.updateReindexOp(lockedReindexOp, { @@ -647,7 +653,7 @@ export const reindexServiceFactory = ( throw new Error(`Reindex operation is not current waiting for reindex task to complete`); } - const resp = await callCluster('tasks.cancel', { + const resp = await callAsUser('tasks.cancel', { taskId: reindexOp.attributes.reindexTaskId, }); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/types.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/types.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/types.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/types.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/worker.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts similarity index 76% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/worker.ts rename to x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts index 628a47be9f5e7..bad6db62efe41 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/reindexing/worker.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts @@ -3,23 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CallCluster, CallClusterWithRequest } from 'src/legacy/core_plugins/elasticsearch'; -import { Request, Server } from 'src/legacy/server/kbn_server'; -import { SavedObjectsClientContract } from 'kibana/server'; - +import { IClusterClient, Logger, SavedObjectsClientContract, FakeRequest } from 'src/core/server'; import moment from 'moment'; -import { XPackInfo } from '../../../../../xpack_main/server/lib/xpack_info'; -import { ReindexSavedObject, ReindexStatus } from '../../../../common/types'; + +import { ReindexSavedObject, ReindexStatus } from '../../../common/types'; import { CredentialStore } from './credential_store'; import { reindexActionsFactory } from './reindex_actions'; import { ReindexService, reindexServiceFactory } from './reindex_service'; +import { LicensingPluginSetup } from '../../../../licensing/server'; const POLL_INTERVAL = 30000; // If no nodes have been able to update this index in 2 minutes (due to missing credentials), set to paused. const PAUSE_WINDOW = POLL_INTERVAL * 4; -const LOG_TAGS = ['upgrade_assistant', 'reindex_worker']; - /** * A singleton worker that will coordinate two polling loops: * (1) A longer loop that polls for reindex operations that are in progress. If any are found, loop (2) is started. @@ -41,24 +37,27 @@ export class ReindexWorker { private timeout?: NodeJS.Timeout; private inProgressOps: ReindexSavedObject[] = []; private readonly reindexService: ReindexService; + private readonly log: Logger; constructor( private client: SavedObjectsClientContract, private credentialStore: CredentialStore, - private callWithRequest: CallClusterWithRequest, - private callWithInternalUser: CallCluster, - private xpackInfo: XPackInfo, - private readonly log: Server['log'] + private clusterClient: IClusterClient, + log: Logger, + private licensing: LicensingPluginSetup ) { + this.log = log.get('reindex_worker'); if (ReindexWorker.workerSingleton) { throw new Error(`More than one ReindexWorker cannot be created.`); } + const callAsInternalUser = this.clusterClient.callAsInternalUser.bind(this.clusterClient); + this.reindexService = reindexServiceFactory( - this.callWithInternalUser, - this.xpackInfo, - reindexActionsFactory(this.client, this.callWithInternalUser), - this.log + callAsInternalUser, + reindexActionsFactory(this.client, callAsInternalUser), + log, + this.licensing ); ReindexWorker.workerSingleton = this; @@ -68,7 +67,7 @@ export class ReindexWorker { * Begins loop (1) to begin checking for in progress reindex operations. */ public start = () => { - this.log(['debug', ...LOG_TAGS], `Starting worker...`); + this.log.debug('Starting worker...'); this.continuePolling = true; this.pollForOperations(); }; @@ -77,7 +76,7 @@ export class ReindexWorker { * Stops the worker from processing any further reindex operations. */ public stop = () => { - this.log(['debug', ...LOG_TAGS], `Stopping worker...`); + this.log.debug('Stopping worker...'); if (this.timeout) { clearTimeout(this.timeout); } @@ -107,7 +106,7 @@ export class ReindexWorker { this.updateOperationLoopRunning = true; while (this.inProgressOps.length > 0) { - this.log(['debug', ...LOG_TAGS], `Updating ${this.inProgressOps.length} reindex operations`); + this.log.debug(`Updating ${this.inProgressOps.length} reindex operations`); // Push each operation through the state machine and refresh. await Promise.all(this.inProgressOps.map(this.processNextStep)); @@ -118,7 +117,7 @@ export class ReindexWorker { }; private pollForOperations = async () => { - this.log(['debug', ...LOG_TAGS], `Polling for reindex operations`); + this.log.debug(`Polling for reindex operations`); await this.refresh(); @@ -131,7 +130,7 @@ export class ReindexWorker { try { this.inProgressOps = await this.reindexService.findAllByStatus(ReindexStatus.inProgress); } catch (e) { - this.log(['debug', ...LOG_TAGS], `Could not fetch riendex operations from Elasticsearch`); + this.log.debug(`Could not fetch reindex operations from Elasticsearch`); this.inProgressOps = []; } @@ -159,10 +158,13 @@ export class ReindexWorker { } // Setup a ReindexService specific to these credentials. - const fakeRequest = { headers: credential } as Request; - const callCluster = this.callWithRequest.bind(null, fakeRequest) as CallCluster; - const actions = reindexActionsFactory(this.client, callCluster); - const service = reindexServiceFactory(callCluster, this.xpackInfo, actions, this.log); + const fakeRequest: FakeRequest = { headers: credential }; + + const scopedClusterClient = this.clusterClient.asScoped(fakeRequest); + const callAsCurrentUser = scopedClusterClient.callAsCurrentUser.bind(scopedClusterClient); + const actions = reindexActionsFactory(this.client, callAsCurrentUser); + + const service = reindexServiceFactory(callAsCurrentUser, actions, this.log, this.licensing); reindexOp = await swallowExceptions(service.processNextStep, this.log)(reindexOp); // Update credential store with most recent state. @@ -176,18 +178,15 @@ export class ReindexWorker { */ const swallowExceptions = ( func: (reindexOp: ReindexSavedObject) => Promise, - log: Server['log'] + log: Logger ) => async (reindexOp: ReindexSavedObject) => { try { return await func(reindexOp); } catch (e) { if (reindexOp.attributes.locked) { - log(['debug', ...LOG_TAGS], `Skipping reindexOp with unexpired lock: ${reindexOp.id}`); + log.debug(`Skipping reindexOp with unexpired lock: ${reindexOp.id}`); } else { - log( - ['warning', ...LOG_TAGS], - `Error when trying to process reindexOp (${reindexOp.id}): ${e.toString()}` - ); + log.warn(`Error when trying to process reindexOp (${reindexOp.id}): ${e.toString()}`); } return reindexOp; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts similarity index 51% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts index 5f95f6e9fd555..703351c45ba5a 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; +import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types'; -import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../../common/types'; import { upsertUIOpenOption } from './es_ui_open_apis'; /** @@ -13,54 +14,29 @@ import { upsertUIOpenOption } from './es_ui_open_apis'; * more thoroughly in the lib/telemetry tests. */ describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => { - const mockIncrementCounter = jest.fn(); - const server = jest.fn().mockReturnValue({ - savedObjects: { - getSavedObjectsRepository: jest.fn().mockImplementation(() => { - return { - incrementCounter: mockIncrementCounter, - }; - }), - }, - plugins: { - elasticsearch: { - getCluster: () => { - return { - callWithInternalUser: {}, - }; - }, - }, - }, - }); - - const request = jest.fn().mockReturnValue({ - payload: { - overview: true, - cluster: true, - indices: true, - }, - }); - describe('Upsert UIOpen Option', () => { it('call saved objects internal repository with the correct info', async () => { - const serverMock = server(); - const incCounterSORepoFunc = serverMock.savedObjects.getSavedObjectsRepository() - .incrementCounter; + const internalRepo = savedObjectsRepositoryMock.create(); - await upsertUIOpenOption(serverMock, request()); + await upsertUIOpenOption({ + overview: true, + cluster: true, + indices: true, + savedObjects: { createInternalRepository: () => internalRepo } as any, + }); - expect(incCounterSORepoFunc).toHaveBeenCalledTimes(3); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(3); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_open.overview` ); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_open.cluster` ); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_open.indices` diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts new file mode 100644 index 0000000000000..64e9b0f217555 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts @@ -0,0 +1,58 @@ +/* + * 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 { SavedObjectsServiceStart } from 'src/core/server'; +import { + UIOpen, + UIOpenOption, + UPGRADE_ASSISTANT_DOC_ID, + UPGRADE_ASSISTANT_TYPE, +} from '../../../common/types'; + +interface IncrementUIOpenDependencies { + uiOpenOptionCounter: UIOpenOption; + savedObjects: SavedObjectsServiceStart; +} + +async function incrementUIOpenOptionCounter({ + savedObjects, + uiOpenOptionCounter, +}: IncrementUIOpenDependencies) { + const internalRepository = savedObjects.createInternalRepository(); + + await internalRepository.incrementCounter( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + `ui_open.${uiOpenOptionCounter}` + ); +} + +type UpsertUIOpenOptionDependencies = UIOpen & { savedObjects: SavedObjectsServiceStart }; + +export async function upsertUIOpenOption({ + overview, + cluster, + indices, + savedObjects, +}: UpsertUIOpenOptionDependencies): Promise { + if (overview) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'overview' }); + } + + if (cluster) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'cluster' }); + } + + if (indices) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'indices' }); + } + + return { + overview, + cluster, + indices, + }; +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts similarity index 52% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts index 3f2c80f7d6b75..31e4e3f07b5de 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../../common/types'; +import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; +import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types'; import { upsertUIReindexOption } from './es_ui_reindex_apis'; /** @@ -13,60 +13,34 @@ import { upsertUIReindexOption } from './es_ui_reindex_apis'; * more thoroughly in the lib/telemetry tests. */ describe('Upgrade Assistant Telemetry SavedObject UIReindex', () => { - const mockIncrementCounter = jest.fn(); - const server = jest.fn().mockReturnValue({ - savedObjects: { - getSavedObjectsRepository: jest.fn().mockImplementation(() => { - return { - incrementCounter: mockIncrementCounter, - }; - }), - }, - plugins: { - elasticsearch: { - getCluster: () => { - return { - callWithInternalUser: {}, - }; - }, - }, - }, - }); - - const request = jest.fn().mockReturnValue({ - payload: { - close: true, - open: true, - start: true, - stop: true, - }, - }); - describe('Upsert UIReindex Option', () => { it('call saved objects internal repository with the correct info', async () => { - const serverMock = server(); - const incCounterSORepoFunc = serverMock.savedObjects.getSavedObjectsRepository() - .incrementCounter; - - await upsertUIReindexOption(serverMock, request()); + const internalRepo = savedObjectsRepositoryMock.create(); + await upsertUIReindexOption({ + close: true, + open: true, + start: true, + stop: true, + savedObjects: { createInternalRepository: () => internalRepo } as any, + }); - expect(incCounterSORepoFunc).toHaveBeenCalledTimes(4); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(4); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_reindex.close` ); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_reindex.open` ); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_reindex.start` ); - expect(incCounterSORepoFunc).toHaveBeenCalledWith( + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, `ui_reindex.stop` diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts new file mode 100644 index 0000000000000..0aaaf63196d67 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsServiceStart } from 'src/core/server'; +import { + UIReindex, + UIReindexOption, + UPGRADE_ASSISTANT_DOC_ID, + UPGRADE_ASSISTANT_TYPE, +} from '../../../common/types'; + +interface IncrementUIReindexOptionDependencies { + uiReindexOptionCounter: UIReindexOption; + savedObjects: SavedObjectsServiceStart; +} + +async function incrementUIReindexOptionCounter({ + savedObjects, + uiReindexOptionCounter, +}: IncrementUIReindexOptionDependencies) { + const internalRepository = savedObjects.createInternalRepository(); + + await internalRepository.incrementCounter( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + `ui_reindex.${uiReindexOptionCounter}` + ); +} + +type UpsertUIReindexOptionDepencies = UIReindex & { savedObjects: SavedObjectsServiceStart }; + +export async function upsertUIReindexOption({ + start, + close, + open, + stop, + savedObjects, +}: UpsertUIReindexOptionDepencies): Promise { + if (close) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'close' }); + } + + if (open) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'open' }); + } + + if (start) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'start' }); + } + + if (stop) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'stop' }); + } + + return { + close, + open, + start, + stop, + }; +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/index.ts similarity index 100% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts rename to x-pack/plugins/upgrade_assistant/server/lib/telemetry/index.ts diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts similarity index 78% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index 27a0eef0d16f6..a4833d9a3d7fe 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { registerUpgradeAssistantUsageCollector } from './usage_collector'; +import { IClusterClient } from 'src/core/server'; /** * Since these route callbacks are so thin, these serve simply as integration tests @@ -14,20 +15,31 @@ import { registerUpgradeAssistantUsageCollector } from './usage_collector'; describe('Upgrade Assistant Usage Collector', () => { let makeUsageCollectorStub: any; let registerStub: any; - let server: any; + let dependencies: any; let callClusterStub: any; let usageCollection: any; + let clusterClient: IClusterClient; beforeEach(() => { + clusterClient = elasticsearchServiceMock.createClusterClient(); + (clusterClient.callAsInternalUser as jest.Mock).mockResolvedValue({ + persistent: {}, + transient: { + logger: { + deprecation: 'WARN', + }, + }, + }); makeUsageCollectorStub = jest.fn(); registerStub = jest.fn(); usageCollection = { makeUsageCollector: makeUsageCollectorStub, registerCollector: registerStub, }; - server = jest.fn().mockReturnValue({ + dependencies = { + usageCollection, savedObjects: { - getSavedObjectsRepository: jest.fn().mockImplementation(() => { + createInternalRepository: jest.fn().mockImplementation(() => { return { get: () => { return { @@ -45,31 +57,26 @@ describe('Upgrade Assistant Usage Collector', () => { }; }), }, - }); - callClusterStub = jest.fn().mockResolvedValue({ - persistent: {}, - transient: { - logger: { - deprecation: 'WARN', - }, + elasticsearch: { + adminClient: clusterClient, }, - }); + }; }); describe('registerUpgradeAssistantUsageCollector', () => { it('should registerCollector', () => { - registerUpgradeAssistantUsageCollector(usageCollection, server()); + registerUpgradeAssistantUsageCollector(dependencies); expect(registerStub).toHaveBeenCalledTimes(1); }); it('should call makeUsageCollector with type = upgrade-assistant', () => { - registerUpgradeAssistantUsageCollector(usageCollection, server()); + registerUpgradeAssistantUsageCollector(dependencies); expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('upgrade-assistant-telemetry'); }); it('fetchUpgradeAssistantMetrics should return correct info', async () => { - registerUpgradeAssistantUsageCollector(usageCollection, server()); + registerUpgradeAssistantUsageCollector(dependencies); const upgradeAssistantStats = await makeUsageCollectorStub.mock.calls[0][0].fetch( callClusterStub ); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts similarity index 72% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts rename to x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index 1d24d190fa9f2..79d6e53c64ec0 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -5,8 +5,12 @@ */ import { set } from 'lodash'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsRepository } from 'src/core/server/saved_objects/service/lib/repository'; +import { + APICaller, + ElasticsearchServiceSetup, + ISavedObjectsRepository, + SavedObjectsServiceStart, +} from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { UPGRADE_ASSISTANT_DOC_ID, @@ -14,12 +18,11 @@ import { UpgradeAssistantTelemetry, UpgradeAssistantTelemetrySavedObject, UpgradeAssistantTelemetrySavedObjectAttributes, -} from '../../../../common/types'; -import { ServerShim } from '../../types'; +} from '../../../common/types'; import { isDeprecationLoggingEnabled } from '../es_deprecation_logging_apis'; async function getSavedObjectAttributesFromRepo( - savedObjectsRepository: SavedObjectsRepository, + savedObjectsRepository: ISavedObjectsRepository, docType: string, docID: string ) { @@ -35,9 +38,9 @@ async function getSavedObjectAttributesFromRepo( } } -async function getDeprecationLoggingStatusValue(callCluster: any): Promise { +async function getDeprecationLoggingStatusValue(callAsCurrentUser: APICaller): Promise { try { - const loggerDeprecationCallResult = await callCluster('cluster.getSettings', { + const loggerDeprecationCallResult = await callAsCurrentUser('cluster.getSettings', { includeDefaults: true, }); @@ -48,17 +51,17 @@ async function getDeprecationLoggingStatusValue(callCluster: any): Promise { - const { getSavedObjectsRepository } = server.savedObjects; - const savedObjectsRepository = getSavedObjectsRepository(callCluster); + const savedObjectsRepository = savedObjects.createInternalRepository(); const upgradeAssistantSOAttributes = await getSavedObjectAttributesFromRepo( savedObjectsRepository, UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID ); - const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue(callCluster); + const callAsInternalUser = adminClient.callAsInternalUser.bind(adminClient); + const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue(callAsInternalUser); const getTelemetrySavedObject = ( upgradeAssistantTelemetrySavedObjectAttrs: UpgradeAssistantTelemetrySavedObjectAttributes | null @@ -103,14 +106,21 @@ export async function fetchUpgradeAssistantMetrics( }; } -export function registerUpgradeAssistantUsageCollector( - usageCollection: UsageCollectionSetup, - server: ServerShim -) { +interface Dependencies { + elasticsearch: ElasticsearchServiceSetup; + savedObjects: SavedObjectsServiceStart; + usageCollection: UsageCollectionSetup; +} + +export function registerUpgradeAssistantUsageCollector({ + elasticsearch, + usageCollection, + savedObjects, +}: Dependencies) { const upgradeAssistantUsageCollector = usageCollection.makeUsageCollector({ type: UPGRADE_ASSISTANT_TYPE, isReady: () => true, - fetch: async (callCluster: any) => fetchUpgradeAssistantMetrics(callCluster, server), + fetch: async () => fetchUpgradeAssistantMetrics(elasticsearch, savedObjects), }); usageCollection.registerCollector(upgradeAssistantUsageCollector); diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts new file mode 100644 index 0000000000000..6ccd073a9e020 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -0,0 +1,125 @@ +/* + * 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 { + Plugin, + CoreSetup, + CoreStart, + PluginInitializerContext, + Logger, + ElasticsearchServiceSetup, + SavedObjectsClient, + SavedObjectsServiceStart, +} from '../../../../src/core/server'; + +import { CloudSetup } from '../../cloud/server'; +import { LicensingPluginSetup } from '../../licensing/server'; + +import { CredentialStore, credentialStoreFactory } from './lib/reindexing/credential_store'; +import { ReindexWorker } from './lib/reindexing'; +import { registerUpgradeAssistantUsageCollector } from './lib/telemetry'; +import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; +import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; +import { registerReindexIndicesRoutes, createReindexWorker } from './routes/reindex_indices'; +import { registerTelemetryRoutes } from './routes/telemetry'; +import { RouteDependencies } from './types'; + +interface PluginsSetup { + usageCollection: UsageCollectionSetup; + licensing: LicensingPluginSetup; + cloud?: CloudSetup; +} + +export class UpgradeAssistantServerPlugin implements Plugin { + private readonly logger: Logger; + private readonly credentialStore: CredentialStore; + + // Properties set at setup + private licensing?: LicensingPluginSetup; + private elasticSearchService?: ElasticsearchServiceSetup; + + // Properties set at start + private savedObjectsServiceStart?: SavedObjectsServiceStart; + private worker?: ReindexWorker; + + constructor({ logger }: PluginInitializerContext) { + this.logger = logger.get(); + this.credentialStore = credentialStoreFactory(); + } + + private getWorker() { + if (!this.worker) { + throw new Error('Worker unavailable'); + } + return this.worker; + } + + setup( + { http, elasticsearch, getStartServices, capabilities }: CoreSetup, + { usageCollection, cloud, licensing }: PluginsSetup + ) { + this.elasticSearchService = elasticsearch; + this.licensing = licensing; + + const router = http.createRouter(); + + const dependencies: RouteDependencies = { + cloud, + router, + credentialStore: this.credentialStore, + log: this.logger, + getSavedObjectsService: () => { + if (!this.savedObjectsServiceStart) { + throw new Error('Saved Objects Start service not available'); + } + return this.savedObjectsServiceStart; + }, + licensing, + }; + + registerClusterCheckupRoutes(dependencies); + registerDeprecationLoggingRoutes(dependencies); + registerReindexIndicesRoutes(dependencies, this.getWorker.bind(this)); + // Bootstrap the needed routes and the collector for the telemetry + registerTelemetryRoutes(dependencies); + + if (usageCollection) { + getStartServices().then(([{ savedObjects }]) => { + registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, savedObjects }); + }); + } + } + + start({ savedObjects }: CoreStart) { + this.savedObjectsServiceStart = savedObjects; + + // The ReindexWorker uses a map of request headers that contain the authentication credentials + // for a given reindex. We cannot currently store these in an the .kibana index b/c we do not + // want to expose these credentials to any unauthenticated users. We also want to avoid any need + // to add a user for a special index just for upgrading. This in-memory cache allows us to + // process jobs without the browser staying on the page, but will require that jobs go into + // a paused state if no Kibana nodes have the required credentials. + + this.worker = createReindexWorker({ + credentialStore: this.credentialStore, + licensing: this.licensing!, + elasticsearchService: this.elasticSearchService!, + logger: this.logger, + savedObjects: new SavedObjectsClient( + this.savedObjectsServiceStart.createInternalRepository() + ), + }); + + this.worker.start(); + } + + stop(): void { + if (this.worker) { + this.worker.stop(); + } + } +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/request.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts similarity index 92% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/request.mock.ts rename to x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts index d09a66dbb4326..fb68e188bb255 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/request.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts @@ -6,7 +6,7 @@ export const createRequestMock = (opts?: { headers?: any; params?: Record; - payload?: Record; + body?: Record; }) => { return Object.assign({ headers: {} }, opts || {}); }; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts similarity index 72% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/routes.mock.ts rename to x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts index 3769bc389123e..a8be171dccd98 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/__mocks__/routes.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts @@ -3,7 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandler } from 'kibana/server'; +import { RequestHandler, RequestHandlerContext } from 'src/core/server'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, +} from '../../../../../../src/core/server/mocks'; + +export const routeHandlerContextMock = ({ + core: { + elasticsearch: { + adminClient: elasticsearchServiceMock.createScopedClusterClient(), + dataClient: elasticsearchServiceMock.createScopedClusterClient(), + }, + savedObjects: { client: savedObjectsClientMock.create() }, + }, +} as unknown) as RequestHandlerContext; /** * Creates a very crude mock of the new platform router implementation. This enables use to test diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts similarity index 73% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.test.ts rename to x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts index 3fe2e1797182b..16f8001f8e1de 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/cluster_checkup.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { kibanaResponseFactory } from 'src/core/server'; -import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; import { createRequestMock } from './__mocks__/request.mock'; jest.mock('../lib/es_version_precheck', () => ({ @@ -24,32 +24,22 @@ import { registerClusterCheckupRoutes } from './cluster_checkup'; * more thoroughly in the es_migration_apis test. */ describe('cluster checkup API', () => { - afterEach(() => jest.clearAllMocks()); - let mockRouter: MockRouter; - let serverShim: any; - let ctxMock: any; - let mockPluginsSetup: any; + let routeDependencies: any; beforeEach(() => { mockRouter = createMockRouter(); - mockPluginsSetup = { + routeDependencies = { cloud: { isCloudEnabled: true, }, - }; - ctxMock = { - core: {}, - }; - serverShim = { router: mockRouter, - plugins: { - elasticsearch: { - getCluster: () => ({ callWithRequest: jest.fn() } as any), - } as any, - }, }; - registerClusterCheckupRoutes(serverShim, mockPluginsSetup); + registerClusterCheckupRoutes(routeDependencies); + }); + + afterEach(() => { + jest.resetAllMocks(); }); describe('with cloud enabled', () => { @@ -62,11 +52,11 @@ describe('cluster checkup API', () => { nodes: [], }); - await serverShim.router.getHandler({ + await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/status', - })(ctxMock, createRequestMock(), kibanaResponseFactory); - expect(spy.mock.calls[0][2]).toBe(true); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); + expect(spy.mock.calls[0][1]).toBe(true); }); }); @@ -77,10 +67,10 @@ describe('cluster checkup API', () => { indices: [], nodes: [], }); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/status', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(resp.status).toEqual(200); expect(JSON.stringify(resp.payload)).toMatchInlineSnapshot( @@ -93,10 +83,10 @@ describe('cluster checkup API', () => { e.status = 403; MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(e); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/status', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(resp.status).toEqual(403); }); @@ -104,10 +94,10 @@ describe('cluster checkup API', () => { it('returns an 500 error if it throws', async () => { MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(new Error(`scary error!`)); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/status', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(resp.status).toEqual(500); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts new file mode 100644 index 0000000000000..22a121ab78683 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.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 { getUpgradeAssistantStatus } from '../lib/es_migration_apis'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; +import { RouteDependencies } from '../types'; + +export function registerClusterCheckupRoutes({ cloud, router }: RouteDependencies) { + const isCloudEnabled = Boolean(cloud?.isCloudEnabled); + + router.get( + { + path: '/api/upgrade_assistant/status', + validate: false, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + elasticsearch: { dataClient }, + }, + }, + request, + response + ) => { + try { + return response.ok({ + body: await getUpgradeAssistantStatus(dataClient, isCloudEnabled), + }); + } catch (e) { + if (e.status === 403) { + return response.forbidden(e.message); + } + + return response.internalError({ body: e }); + } + } + ) + ); +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts similarity index 56% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.test.ts rename to x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index d663361956374..845a0238f7918 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -5,7 +5,7 @@ */ import { kibanaResponseFactory } from 'src/core/server'; -import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; import { createRequestMock } from './__mocks__/request.mock'; jest.mock('../lib/es_version_precheck', () => ({ @@ -21,42 +21,42 @@ import { registerDeprecationLoggingRoutes } from './deprecation_logging'; */ describe('deprecation logging API', () => { let mockRouter: MockRouter; - let serverShim: any; - let callWithRequest: any; - const ctxMock: any = {}; + let routeDependencies: any; beforeEach(() => { mockRouter = createMockRouter(); - callWithRequest = jest.fn(); - serverShim = { + routeDependencies = { router: mockRouter, - plugins: { - elasticsearch: { - getCluster: () => ({ callWithRequest } as any), - } as any, - }, }; - registerDeprecationLoggingRoutes(serverShim); + registerDeprecationLoggingRoutes(routeDependencies); + }); + + afterEach(() => { + jest.resetAllMocks(); }); describe('GET /api/upgrade_assistant/deprecation_logging', () => { it('returns isEnabled', async () => { - callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'WARN' } } }); - const resp = await serverShim.router.getHandler({ + (routeHandlerContextMock.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock).mockResolvedValue({ + default: { logger: { deprecation: 'WARN' } }, + }); + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(resp.status).toEqual(200); expect(resp.payload).toEqual({ isEnabled: true }); }); it('returns an error if it throws', async () => { - callWithRequest.mockRejectedValue(new Error(`scary error!`)); - const resp = await serverShim.router.getHandler({ + (routeHandlerContextMock.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(resp.status).toEqual(500); }); @@ -64,21 +64,25 @@ describe('deprecation logging API', () => { describe('PUT /api/upgrade_assistant/deprecation_logging', () => { it('returns isEnabled', async () => { - callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'ERROR' } } }); - const resp = await serverShim.router.getHandler({ + (routeHandlerContextMock.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock).mockResolvedValue({ + default: { logger: { deprecation: 'ERROR' } }, + }); + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(resp.payload).toEqual({ isEnabled: false }); }); it('returns an error if it throws', async () => { - callWithRequest.mockRejectedValue(new Error(`scary error!`)); - const resp = await serverShim.router.getHandler({ + (routeHandlerContextMock.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(ctxMock, { body: { isEnabled: false } }, kibanaResponseFactory); + })(routeHandlerContextMock, { body: { isEnabled: false } }, kibanaResponseFactory); expect(resp.status).toEqual(500); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts new file mode 100644 index 0000000000000..739a789c95ce0 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -0,0 +1,72 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import { + getDeprecationLoggingStatus, + setDeprecationLogging, +} from '../lib/es_deprecation_logging_apis'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; +import { RouteDependencies } from '../types'; + +export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) { + router.get( + { + path: '/api/upgrade_assistant/deprecation_logging', + validate: false, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + elasticsearch: { dataClient }, + }, + }, + request, + response + ) => { + try { + const result = await getDeprecationLoggingStatus(dataClient); + return response.ok({ body: result }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ) + ); + + router.put( + { + path: '/api/upgrade_assistant/deprecation_logging', + validate: { + body: schema.object({ + isEnabled: schema.boolean(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + elasticsearch: { dataClient }, + }, + }, + request, + response + ) => { + try { + const { isEnabled } = request.body as { isEnabled: boolean }; + return response.ok({ + body: await setDeprecationLogging(dataClient, isEnabled), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ) + ); +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.test.ts similarity index 80% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.test.ts rename to x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.test.ts index d520324239656..695bb6304cfdf 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/reindex_indices.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; import { kibanaResponseFactory } from 'src/core/server'; -import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { licensingMock } from '../../../licensing/server/mocks'; +import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; import { createRequestMock } from './__mocks__/request.mock'; const mockReindexService = { @@ -31,12 +31,7 @@ jest.mock('../lib/reindexing', () => { }; }); -import { - IndexGroup, - ReindexSavedObject, - ReindexStatus, - ReindexWarning, -} from '../../../common/types'; +import { IndexGroup, ReindexSavedObject, ReindexStatus, ReindexWarning } from '../../common/types'; import { credentialStoreFactory } from '../lib/reindexing/credential_store'; import { registerReindexIndicesRoutes } from './reindex_indices'; @@ -46,9 +41,8 @@ import { registerReindexIndicesRoutes } from './reindex_indices'; * more thoroughly in the es_migration_apis test. */ describe('reindex API', () => { - let serverShim: any; + let routeDependencies: any; let mockRouter: MockRouter; - let ctxMock: any; const credentialStore = credentialStoreFactory(); const worker = { @@ -57,24 +51,13 @@ describe('reindex API', () => { } as any; beforeEach(() => { - ctxMock = { - core: { - savedObjects: savedObjectsClientMock.create(), - }, - }; mockRouter = createMockRouter(); - serverShim = { + routeDependencies = { + credentialStore, router: mockRouter, - plugins: { - xpack_main: { - info: jest.fn(), - }, - elasticsearch: { - getCluster: () => ({ callWithRequest: jest.fn() } as any), - } as any, - }, + licensing: licensingMock.createSetup(), }; - registerReindexIndicesRoutes(serverShim, worker, credentialStore); + registerReindexIndicesRoutes(routeDependencies, () => worker); mockReindexService.hasRequiredPrivileges.mockResolvedValue(true); mockReindexService.detectReindexWarnings.mockReset(); @@ -92,7 +75,9 @@ describe('reindex API', () => { credentialStore.clear(); }); - afterEach(() => jest.clearAllMocks()); + afterEach(() => { + jest.resetAllMocks(); + }); describe('GET /api/upgrade_assistant/reindex/{indexName}', () => { it('returns the attributes of the reindex operation and reindex warnings', async () => { @@ -101,10 +86,14 @@ describe('reindex API', () => { }); mockReindexService.detectReindexWarnings.mockResolvedValueOnce([ReindexWarning.allField]); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', - })(ctxMock, createRequestMock({ params: { indexName: 'wowIndex' } }), kibanaResponseFactory); + })( + routeHandlerContextMock, + createRequestMock({ params: { indexName: 'wowIndex' } }), + kibanaResponseFactory + ); // It called into the service correctly expect(mockReindexService.findReindexOperation).toHaveBeenCalledWith('wowIndex'); @@ -121,10 +110,14 @@ describe('reindex API', () => { mockReindexService.findReindexOperation.mockResolvedValueOnce(null); mockReindexService.detectReindexWarnings.mockResolvedValueOnce(null); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', - })(ctxMock, createRequestMock({ params: { indexName: 'anIndex' } }), kibanaResponseFactory); + })( + routeHandlerContextMock, + createRequestMock({ params: { indexName: 'anIndex' } }), + kibanaResponseFactory + ); expect(resp.status).toEqual(200); const data = resp.payload; @@ -137,10 +130,14 @@ describe('reindex API', () => { mockReindexService.detectReindexWarnings.mockResolvedValueOnce([]); mockReindexService.getIndexGroup.mockReturnValue(IndexGroup.ml); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', - })(ctxMock, createRequestMock({ params: { indexName: 'anIndex' } }), kibanaResponseFactory); + })( + routeHandlerContextMock, + createRequestMock({ params: { indexName: 'anIndex' } }), + kibanaResponseFactory + ); expect(resp.status).toEqual(200); const data = resp.payload; @@ -154,10 +151,14 @@ describe('reindex API', () => { attributes: { indexName: 'theIndex' }, }); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'post', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', - })(ctxMock, createRequestMock({ params: { indexName: 'theIndex' } }), kibanaResponseFactory); + })( + routeHandlerContextMock, + createRequestMock({ params: { indexName: 'theIndex' } }), + kibanaResponseFactory + ); // It called create correctly expect(mockReindexService.createReindexOperation).toHaveBeenCalledWith('theIndex'); @@ -173,10 +174,14 @@ describe('reindex API', () => { attributes: { indexName: 'theIndex' }, }); - await serverShim.router.getHandler({ + await routeDependencies.router.getHandler({ method: 'post', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', - })(ctxMock, createRequestMock({ params: { indexName: 'theIndex' } }), kibanaResponseFactory); + })( + routeHandlerContextMock, + createRequestMock({ params: { indexName: 'theIndex' } }), + kibanaResponseFactory + ); expect(worker.forceRefresh).toHaveBeenCalled(); }); @@ -187,11 +192,11 @@ describe('reindex API', () => { } as ReindexSavedObject; mockReindexService.createReindexOperation.mockResolvedValueOnce(reindexOp); - await serverShim.router.getHandler({ + await routeDependencies.router.getHandler({ method: 'post', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ headers: { 'kbn-auth-x': 'HERE!', @@ -212,11 +217,11 @@ describe('reindex API', () => { attributes: { indexName: 'theIndex', status: ReindexStatus.inProgress }, }); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'post', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ params: { indexName: 'theIndex' }, }), @@ -235,11 +240,11 @@ describe('reindex API', () => { it('returns a 403 if required privileges fails', async () => { mockReindexService.hasRequiredPrivileges.mockResolvedValueOnce(false); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'post', pathPattern: '/api/upgrade_assistant/reindex/{indexName}', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ params: { indexName: 'theIndex' }, }), @@ -254,11 +259,11 @@ describe('reindex API', () => { it('returns a 501', async () => { mockReindexService.cancelReindexing.mockResolvedValueOnce({}); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'post', pathPattern: '/api/upgrade_assistant/reindex/{indexName}/cancel', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ params: { indexName: 'cancelMe' }, }), diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts new file mode 100644 index 0000000000000..a910145474061 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices.ts @@ -0,0 +1,217 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { Logger, ElasticsearchServiceSetup, SavedObjectsClient } from 'src/core/server'; +import { ReindexStatus } from '../../common/types'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; +import { reindexServiceFactory, ReindexWorker } from '../lib/reindexing'; +import { CredentialStore } from '../lib/reindexing/credential_store'; +import { reindexActionsFactory } from '../lib/reindexing/reindex_actions'; +import { RouteDependencies } from '../types'; +import { LicensingPluginSetup } from '../../../licensing/server'; + +interface CreateReindexWorker { + logger: Logger; + elasticsearchService: ElasticsearchServiceSetup; + credentialStore: CredentialStore; + savedObjects: SavedObjectsClient; + licensing: LicensingPluginSetup; +} + +export function createReindexWorker({ + logger, + elasticsearchService, + credentialStore, + savedObjects, + licensing, +}: CreateReindexWorker) { + const { adminClient } = elasticsearchService; + return new ReindexWorker(savedObjects, credentialStore, adminClient, logger, licensing); +} + +export function registerReindexIndicesRoutes( + { credentialStore, router, licensing, log }: RouteDependencies, + getWorker: () => ReindexWorker +) { + const BASE_PATH = '/api/upgrade_assistant/reindex'; + + // Start reindex for an index + router.post( + { + path: `${BASE_PATH}/{indexName}`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + savedObjects, + elasticsearch: { dataClient }, + }, + }, + request, + response + ) => { + const { indexName } = request.params as any; + const { client } = savedObjects; + const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const reindexActions = reindexActionsFactory(client, callAsCurrentUser); + const reindexService = reindexServiceFactory( + callAsCurrentUser, + reindexActions, + log, + licensing + ); + + try { + if (!(await reindexService.hasRequiredPrivileges(indexName))) { + return response.forbidden({ + body: `You do not have adequate privileges to reindex this index.`, + }); + } + + const existingOp = await reindexService.findReindexOperation(indexName); + + // If the reindexOp already exists and it's paused, resume it. Otherwise create a new one. + const reindexOp = + existingOp && existingOp.attributes.status === ReindexStatus.paused + ? await reindexService.resumeReindexOperation(indexName) + : await reindexService.createReindexOperation(indexName); + + // Add users credentials for the worker to use + credentialStore.set(reindexOp, request.headers); + + // Kick the worker on this node to immediately pickup the new reindex operation. + getWorker().forceRefresh(); + + return response.ok({ body: reindexOp.attributes }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ) + ); + + // Get status + router.get( + { + path: `${BASE_PATH}/{indexName}`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + savedObjects, + elasticsearch: { dataClient }, + }, + }, + request, + response + ) => { + const { client } = savedObjects; + const { indexName } = request.params as any; + const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const reindexActions = reindexActionsFactory(client, callAsCurrentUser); + const reindexService = reindexServiceFactory( + callAsCurrentUser, + reindexActions, + log, + licensing + ); + + try { + const hasRequiredPrivileges = await reindexService.hasRequiredPrivileges(indexName); + const reindexOp = await reindexService.findReindexOperation(indexName); + // If the user doesn't have privileges than querying for warnings is going to fail. + const warnings = hasRequiredPrivileges + ? await reindexService.detectReindexWarnings(indexName) + : []; + const indexGroup = reindexService.getIndexGroup(indexName); + + return response.ok({ + body: { + reindexOp: reindexOp ? reindexOp.attributes : null, + warnings, + indexGroup, + hasRequiredPrivileges, + }, + }); + } catch (e) { + if (!e.isBoom) { + return response.internalError({ body: e }); + } + return response.customError({ + body: { + message: e.message, + }, + statusCode: e.statusCode, + }); + } + } + ) + ); + + // Cancel reindex + router.post( + { + path: `${BASE_PATH}/{indexName}/cancel`, + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + savedObjects, + elasticsearch: { dataClient }, + }, + }, + request, + response + ) => { + const { indexName } = request.params as any; + const { client } = savedObjects; + const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const reindexActions = reindexActionsFactory(client, callAsCurrentUser); + const reindexService = reindexServiceFactory( + callAsCurrentUser, + reindexActions, + log, + licensing + ); + + try { + await reindexService.cancelReindexing(indexName); + + return response.ok({ body: { acknowledged: true } }); + } catch (e) { + if (!e.isBoom) { + return response.internalError({ body: e }); + } + return response.customError({ + body: { + message: e.message, + }, + statusCode: e.statusCode, + }); + } + } + ) + ); +} diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts similarity index 79% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.test.ts rename to x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts index 582c75e3701b6..b2b8ccf1ca57a 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts @@ -5,7 +5,8 @@ */ import { kibanaResponseFactory } from 'src/core/server'; -import { createMockRouter, MockRouter } from './__mocks__/routes.mock'; +import { savedObjectsServiceMock } from 'src/core/server/saved_objects/saved_objects_service.mock'; +import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; import { createRequestMock } from './__mocks__/request.mock'; jest.mock('../lib/telemetry/es_ui_open_apis', () => ({ @@ -26,24 +27,15 @@ import { registerTelemetryRoutes } from './telemetry'; * more thoroughly in the lib/telemetry tests. */ describe('Upgrade Assistant Telemetry API', () => { - let serverShim: any; + let routeDependencies: any; let mockRouter: MockRouter; - let ctxMock: any; beforeEach(() => { - ctxMock = {}; mockRouter = createMockRouter(); - serverShim = { + routeDependencies = { + getSavedObjectsService: () => savedObjectsServiceMock.create(), router: mockRouter, - plugins: { - xpack_main: { - info: jest.fn(), - }, - elasticsearch: { - getCluster: () => ({ callWithRequest: jest.fn() } as any), - } as any, - }, }; - registerTelemetryRoutes(serverShim); + registerTelemetryRoutes(routeDependencies); }); afterEach(() => jest.clearAllMocks()); @@ -57,10 +49,14 @@ describe('Upgrade Assistant Telemetry API', () => { (upsertUIOpenOption as jest.Mock).mockResolvedValue(returnPayload); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/telemetry/ui_open', - })(ctxMock, createRequestMock(), kibanaResponseFactory); + })( + routeHandlerContextMock, + createRequestMock({ body: returnPayload }), + kibanaResponseFactory + ); expect(resp.payload).toEqual(returnPayload); }); @@ -74,13 +70,13 @@ describe('Upgrade Assistant Telemetry API', () => { (upsertUIOpenOption as jest.Mock).mockResolvedValue(returnPayload); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/telemetry/ui_open', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ - payload: { + body: { overview: true, cluster: true, indices: true, @@ -95,13 +91,13 @@ describe('Upgrade Assistant Telemetry API', () => { it('returns an error if it throws', async () => { (upsertUIOpenOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/telemetry/ui_open', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ - payload: { + body: { overview: false, }, }), @@ -123,13 +119,13 @@ describe('Upgrade Assistant Telemetry API', () => { (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ - payload: { + body: { overview: false, }, }), @@ -149,13 +145,13 @@ describe('Upgrade Assistant Telemetry API', () => { (upsertUIReindexOption as jest.Mock).mockRejectedValue(returnPayload); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ - payload: { + body: { close: true, open: true, start: true, @@ -171,13 +167,13 @@ describe('Upgrade Assistant Telemetry API', () => { it('returns an error if it throws', async () => { (upsertUIReindexOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); - const resp = await serverShim.router.getHandler({ + const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', })( - ctxMock, + routeHandlerContextMock, createRequestMock({ - payload: { + body: { start: false, }, }), diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts similarity index 66% rename from x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.ts rename to x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts index f08c49809033d..900a5e64c55c3 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/routes/telemetry.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -7,11 +7,10 @@ import { schema } from '@kbn/config-schema'; import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; -import { ServerShimWithRouter } from '../types'; -import { createRequestShim } from './create_request_shim'; +import { RouteDependencies } from '../types'; -export function registerTelemetryRoutes(server: ServerShimWithRouter) { - server.router.put( +export function registerTelemetryRoutes({ router, getSavedObjectsService }: RouteDependencies) { + router.put( { path: '/api/upgrade_assistant/telemetry/ui_open', validate: { @@ -23,16 +22,23 @@ export function registerTelemetryRoutes(server: ServerShimWithRouter) { }, }, async (ctx, request, response) => { - const reqShim = createRequestShim(request); + const { cluster, indices, overview } = request.body; try { - return response.ok({ body: await upsertUIOpenOption(server, reqShim) }); + return response.ok({ + body: await upsertUIOpenOption({ + savedObjects: getSavedObjectsService(), + cluster, + indices, + overview, + }), + }); } catch (e) { return response.internalError({ body: e }); } } ); - server.router.put( + router.put( { path: '/api/upgrade_assistant/telemetry/ui_reindex', validate: { @@ -45,9 +51,17 @@ export function registerTelemetryRoutes(server: ServerShimWithRouter) { }, }, async (ctx, request, response) => { - const reqShim = createRequestShim(request); + const { close, open, start, stop } = request.body; try { - return response.ok({ body: await upsertUIReindexOption(server, reqShim) }); + return response.ok({ + body: await upsertUIReindexOption({ + savedObjects: getSavedObjectsService(), + close, + open, + start, + stop, + }), + }); } catch (e) { return response.internalError({ body: e }); } diff --git a/x-pack/plugins/upgrade_assistant/server/types.ts b/x-pack/plugins/upgrade_assistant/server/types.ts new file mode 100644 index 0000000000000..3f3beadd2f333 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/types.ts @@ -0,0 +1,19 @@ +/* + * 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 { IRouter, Logger, SavedObjectsServiceStart } from 'src/core/server'; +import { CloudSetup } from '../../cloud/server'; +import { CredentialStore } from './lib/reindexing/credential_store'; +import { LicensingPluginSetup } from '../../licensing/server'; + +export interface RouteDependencies { + router: IRouter; + credentialStore: CredentialStore; + log: Logger; + getSavedObjectsService: () => SavedObjectsServiceStart; + licensing: LicensingPluginSetup; + cloud?: CloudSetup; +} diff --git a/x-pack/plugins/uptime/kibana.json b/x-pack/plugins/uptime/kibana.json new file mode 100644 index 0000000000000..dd61716325afc --- /dev/null +++ b/x-pack/plugins/uptime/kibana.json @@ -0,0 +1,9 @@ +{ + "configPath": ["xpack"], + "id": "uptime", + "kibanaVersion": "kibana", + "requiredPlugins": ["features", "licensing", "usageCollection"], + "server": true, + "ui": false, + "version": "8.0.0" +} diff --git a/x-pack/legacy/plugins/uptime/server/graphql/constants.ts b/x-pack/plugins/uptime/server/graphql/constants.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/constants.ts rename to x-pack/plugins/uptime/server/graphql/constants.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/index.ts b/x-pack/plugins/uptime/server/graphql/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/index.ts rename to x-pack/plugins/uptime/server/graphql/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitor_states/index.ts b/x-pack/plugins/uptime/server/graphql/monitor_states/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/monitor_states/index.ts rename to x-pack/plugins/uptime/server/graphql/monitor_states/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitor_states/resolvers.ts b/x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts similarity index 89% rename from x-pack/legacy/plugins/uptime/server/graphql/monitor_states/resolvers.ts rename to x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts index e2b076d570843..6ac42f7717259 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitor_states/resolvers.ts +++ b/x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts @@ -6,13 +6,13 @@ import { CreateUMGraphQLResolvers, UMContext } from '../types'; import { UMServerLibs } from '../../lib/lib'; -import { UMResolver } from '../../../common/graphql/resolver_types'; +import { UMResolver } from '../../../../../legacy/plugins/uptime/common/graphql/resolver_types'; import { GetMonitorStatesQueryArgs, MonitorSummaryResult, StatesIndexStatus, -} from '../../../common/graphql/types'; -import { CONTEXT_DEFAULTS } from '../../../common/constants/context_defaults'; +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants/context_defaults'; export type UMGetMonitorStatesResolver = UMResolver< MonitorSummaryResult | Promise, diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitor_states/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/monitor_states/schema.gql.ts rename to x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/index.ts b/x-pack/plugins/uptime/server/graphql/monitors/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/monitors/index.ts rename to x-pack/plugins/uptime/server/graphql/monitors/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts b/x-pack/plugins/uptime/server/graphql/monitors/resolvers.ts similarity index 71% rename from x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts rename to x-pack/plugins/uptime/server/graphql/monitors/resolvers.ts index 19f23fa1bb934..b39c5f42bfd75 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts +++ b/x-pack/plugins/uptime/server/graphql/monitors/resolvers.ts @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UMResolver } from '../../../common/graphql/resolver_types'; -import { GetMonitorChartsDataQueryArgs, MonitorChart } from '../../../common/graphql/types'; +import { UMGqlRange } from '../../../../../legacy/plugins/uptime/common/domain_types'; +import { UMResolver } from '../../../../../legacy/plugins/uptime/common/graphql/resolver_types'; +import { + GetMonitorChartsDataQueryArgs, + MonitorChart, +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { UMServerLibs } from '../../lib/lib'; import { CreateUMGraphQLResolvers, UMContext } from '../types'; +export type UMMonitorsResolver = UMResolver, any, UMGqlRange, UMContext>; + export type UMGetMonitorChartsResolver = UMResolver< any | Promise, any, diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/monitors/schema.gql.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts rename to x-pack/plugins/uptime/server/graphql/monitors/schema.gql.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/pings/index.ts b/x-pack/plugins/uptime/server/graphql/pings/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/pings/index.ts rename to x-pack/plugins/uptime/server/graphql/pings/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/pings/resolvers.ts b/x-pack/plugins/uptime/server/graphql/pings/resolvers.ts similarity index 84% rename from x-pack/legacy/plugins/uptime/server/graphql/pings/resolvers.ts rename to x-pack/plugins/uptime/server/graphql/pings/resolvers.ts index de83a9ced16b2..b383fc5d5fb15 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/pings/resolvers.ts +++ b/x-pack/plugins/uptime/server/graphql/pings/resolvers.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UMResolver } from '../../../common/graphql/resolver_types'; -import { AllPingsQueryArgs, PingResults } from '../../../common/graphql/types'; +import { UMResolver } from '../../../../../legacy/plugins/uptime/common/graphql/resolver_types'; +import { + AllPingsQueryArgs, + PingResults, +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { UMServerLibs } from '../../lib/lib'; import { UMContext } from '../types'; import { CreateUMGraphQLResolvers } from '../types'; diff --git a/x-pack/legacy/plugins/uptime/server/graphql/pings/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/pings/schema.gql.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/pings/schema.gql.ts rename to x-pack/plugins/uptime/server/graphql/pings/schema.gql.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/types.ts b/x-pack/plugins/uptime/server/graphql/types.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/types.ts rename to x-pack/plugins/uptime/server/graphql/types.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_literal.test.ts b/x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_literal.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_literal.test.ts rename to x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_literal.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_value.test.ts b/x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_value.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_value.test.ts rename to x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/parse_value.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/serialize.test.ts b/x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/serialize.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/serialize.test.ts rename to x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/__tests__/serialize.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/index.ts b/x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/index.ts rename to x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/resolvers.ts b/x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/resolvers.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/resolvers.ts rename to x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/resolvers.ts diff --git a/x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/schema.gql.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/graphql/unsigned_int_scalar/schema.gql.ts rename to x-pack/plugins/uptime/server/graphql/unsigned_int_scalar/schema.gql.ts diff --git a/x-pack/plugins/uptime/server/index.ts b/x-pack/plugins/uptime/server/index.ts new file mode 100644 index 0000000000000..bec47fa9db4cf --- /dev/null +++ b/x-pack/plugins/uptime/server/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from '../../../../src/core/server'; +import { Plugin } from './plugin'; + +export { initServerWithKibana, KibanaServer } from './kibana.index'; +export const plugin = (initializerContext: PluginInitializerContext) => + new Plugin(initializerContext); diff --git a/x-pack/legacy/plugins/uptime/server/kibana.index.ts b/x-pack/plugins/uptime/server/kibana.index.ts similarity index 82% rename from x-pack/legacy/plugins/uptime/server/kibana.index.ts rename to x-pack/plugins/uptime/server/kibana.index.ts index 73fabc629946b..c7ac3a70c0494 100644 --- a/x-pack/legacy/plugins/uptime/server/kibana.index.ts +++ b/x-pack/plugins/uptime/server/kibana.index.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { Request, Server } from 'hapi'; -import { PLUGIN } from '../common/constants'; +import { PLUGIN } from '../../../legacy/plugins/uptime/common/constants'; import { KibanaTelemetryAdapter } from './lib/adapters/telemetry'; import { compose } from './lib/compose/kibana'; import { initUptimeServer } from './uptime_server'; @@ -25,17 +24,13 @@ export interface KibanaServer extends Server { } export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCorePlugins) => { - const { usageCollection, xpack } = plugins; - const libs = compose(server, plugins); + const { features, usageCollection } = plugins; + const libs = compose(server); KibanaTelemetryAdapter.registerUsageCollector(usageCollection); - initUptimeServer(libs); - - xpack.registerFeature({ + features.registerFeature({ id: PLUGIN.ID, - name: i18n.translate('xpack.uptime.featureRegistry.uptimeFeatureName', { - defaultMessage: 'Uptime', - }), + name: PLUGIN.NAME, navLinkId: PLUGIN.ID, icon: 'uptimeApp', app: ['uptime', 'kibana'], @@ -59,4 +54,6 @@ export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCor }, }, }); + + initUptimeServer(libs); }; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts similarity index 85% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts rename to x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index fb2052bb4c87f..8dde6050d5d36 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -6,13 +6,9 @@ import { GraphQLSchema } from 'graphql'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { - SavedObjectsLegacyService, - IRouter, - CallAPIOptions, - SavedObjectsClientContract, -} from 'src/core/server'; +import { IRouter, CallAPIOptions, SavedObjectsClientContract } from 'src/core/server'; import { UMKibanaRoute } from '../../../rest_api'; +import { PluginSetupContract } from '../../../../../features/server'; type APICaller = ( endpoint: string, @@ -34,9 +30,8 @@ export interface UptimeCoreSetup { } export interface UptimeCorePlugins { - savedObjects: SavedObjectsLegacyService; + features: PluginSetupContract; usageCollection: UsageCollectionSetup; - xpack: any; } export interface UMBackendFrameworkAdapter { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/index.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/framework/index.ts rename to x-pack/plugins/uptime/server/lib/adapters/framework/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts rename to x-pack/plugins/uptime/server/lib/adapters/framework/kibana_framework_adapter.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/index.ts b/x-pack/plugins/uptime/server/lib/adapters/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/index.ts rename to x-pack/plugins/uptime/server/lib/adapters/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/__snapshots__/kibana_telemetry_adapter.test.ts.snap b/x-pack/plugins/uptime/server/lib/adapters/telemetry/__tests__/__snapshots__/kibana_telemetry_adapter.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/__snapshots__/kibana_telemetry_adapter.test.ts.snap rename to x-pack/plugins/uptime/server/lib/adapters/telemetry/__tests__/__snapshots__/kibana_telemetry_adapter.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts rename to x-pack/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/index.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/index.ts rename to x-pack/plugins/uptime/server/lib/adapters/telemetry/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts rename to x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/compose/kibana.ts b/x-pack/plugins/uptime/server/lib/compose/kibana.ts similarity index 80% rename from x-pack/legacy/plugins/uptime/server/lib/compose/kibana.ts rename to x-pack/plugins/uptime/server/lib/compose/kibana.ts index 875a5d9dc8c5c..edda5cb283323 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/compose/kibana.ts +++ b/x-pack/plugins/uptime/server/lib/compose/kibana.ts @@ -8,9 +8,9 @@ import { UMKibanaBackendFrameworkAdapter } from '../adapters/framework'; import * as requests from '../requests'; import { licenseCheck } from '../domains'; import { UMDomainLibs, UMServerLibs } from '../lib'; -import { UptimeCorePlugins, UptimeCoreSetup } from '../adapters/framework'; +import { UptimeCoreSetup } from '../adapters/framework'; -export function compose(server: UptimeCoreSetup, plugins: UptimeCorePlugins): UMServerLibs { +export function compose(server: UptimeCoreSetup): UMServerLibs { const framework = new UMKibanaBackendFrameworkAdapter(server); const domainLibs: UMDomainLibs = { diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/__snapshots__/license.test.ts.snap b/x-pack/plugins/uptime/server/lib/domains/__tests__/__snapshots__/license.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/__snapshots__/license.test.ts.snap rename to x-pack/plugins/uptime/server/lib/domains/__tests__/__snapshots__/license.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts b/x-pack/plugins/uptime/server/lib/domains/__tests__/license.test.ts similarity index 93% rename from x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts rename to x-pack/plugins/uptime/server/lib/domains/__tests__/license.test.ts index 8c47b318da9bd..b842f55fc7579 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/domains/__tests__/license.test.ts +++ b/x-pack/plugins/uptime/server/lib/domains/__tests__/license.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILicense } from '../../../../../../../plugins/licensing/server'; +import { ILicense } from '../../../../../licensing/server'; import { licenseCheck } from '../license'; describe('license check', () => { diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/index.ts b/x-pack/plugins/uptime/server/lib/domains/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/domains/index.ts rename to x-pack/plugins/uptime/server/lib/domains/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts b/x-pack/plugins/uptime/server/lib/domains/license.ts similarity index 93% rename from x-pack/legacy/plugins/uptime/server/lib/domains/license.ts rename to x-pack/plugins/uptime/server/lib/domains/license.ts index b8b5722d79877..d272424379e48 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/domains/license.ts +++ b/x-pack/plugins/uptime/server/lib/domains/license.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILicense } from '../../../../../../plugins/licensing/server'; +import { ILicense } from '../../../../licensing/server'; export interface UMLicenseStatusResponse { statusCode: number; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/assert_close_to.test.ts.snap b/x-pack/plugins/uptime/server/lib/helper/__test__/__snapshots__/assert_close_to.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/assert_close_to.test.ts.snap rename to x-pack/plugins/uptime/server/lib/helper/__test__/__snapshots__/assert_close_to.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/get_filter_clause.test.ts.snap b/x-pack/plugins/uptime/server/lib/helper/__test__/__snapshots__/get_filter_clause.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/get_filter_clause.test.ts.snap rename to x-pack/plugins/uptime/server/lib/helper/__test__/__snapshots__/get_filter_clause.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/assert_close_to.test.ts b/x-pack/plugins/uptime/server/lib/helper/__test__/assert_close_to.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/assert_close_to.test.ts rename to x-pack/plugins/uptime/server/lib/helper/__test__/assert_close_to.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_filter_clause.test.ts b/x-pack/plugins/uptime/server/lib/helper/__test__/get_filter_clause.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_filter_clause.test.ts rename to x-pack/plugins/uptime/server/lib/helper/__test__/get_filter_clause.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts b/x-pack/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts rename to x-pack/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.ts b/x-pack/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.ts rename to x-pack/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts b/x-pack/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts rename to x-pack/plugins/uptime/server/lib/helper/__test__/parse_relative_date.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/assert_close_to.ts b/x-pack/plugins/uptime/server/lib/helper/assert_close_to.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/assert_close_to.ts rename to x-pack/plugins/uptime/server/lib/helper/assert_close_to.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts b/x-pack/plugins/uptime/server/lib/helper/get_filter_clause.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts rename to x-pack/plugins/uptime/server/lib/helper/get_filter_clause.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts b/x-pack/plugins/uptime/server/lib/helper/get_histogram_interval.ts similarity index 95% rename from x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts rename to x-pack/plugins/uptime/server/lib/helper/get_histogram_interval.ts index 26515fb4b4c63..fb44f5727aab3 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts +++ b/x-pack/plugins/uptime/server/lib/helper/get_histogram_interval.ts @@ -5,7 +5,7 @@ */ import DateMath from '@elastic/datemath'; -import { QUERY } from '../../../common/constants'; +import { QUERY } from '../../../../../legacy/plugins/uptime/common/constants'; export const parseRelativeDate = (dateStr: string, options = {}) => { // We need this this parsing because if user selects This week or this date diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.ts b/x-pack/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.ts rename to x-pack/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts b/x-pack/plugins/uptime/server/lib/helper/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/index.ts rename to x-pack/plugins/uptime/server/lib/helper/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/make_date_rate_filter.ts b/x-pack/plugins/uptime/server/lib/helper/make_date_rate_filter.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/make_date_rate_filter.ts rename to x-pack/plugins/uptime/server/lib/helper/make_date_rate_filter.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/object_to_array.ts b/x-pack/plugins/uptime/server/lib/helper/object_to_array.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/helper/object_to_array.ts rename to x-pack/plugins/uptime/server/lib/helper/object_to_array.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/lib.ts rename to x-pack/plugins/uptime/server/lib/lib.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap rename to x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap rename to x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap rename to x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_ping_histogram.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_ping_histogram.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_ping_histogram.test.ts.snap rename to x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_ping_histogram.test.ts.snap diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/combine_range_with_filters.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/combine_range_with_filters.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/combine_range_with_filters.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/combine_range_with_filters.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/extract_filter_aggs_results.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/extract_filter_aggs_results.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/extract_filter_aggs_results.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/extract_filter_aggs_results.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/generate_filter_aggs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/generate_filter_aggs.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/generate_filter_aggs.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/generate_filter_aggs.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts rename to x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json b/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json rename to x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/generate_filter_aggs.ts b/x-pack/plugins/uptime/server/lib/requests/generate_filter_aggs.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/generate_filter_aggs.ts rename to x-pack/plugins/uptime/server/lib/requests/generate_filter_aggs.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_filter_bar.ts b/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts similarity index 94% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_filter_bar.ts rename to x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts index 79259afe2b9eb..affe205a46844 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_filter_bar.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts @@ -5,9 +5,9 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { OverviewFilters } from '../../../common/runtime_types'; +import { OverviewFilters } from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { generateFilterAggs } from './generate_filter_aggs'; -import { INDEX_NAMES } from '../../../common/constants'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export interface GetFilterBarParams { /** @param dateRangeStart timestamp bounds */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_index_pattern.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts similarity index 92% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_index_pattern.ts rename to x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts index 4b40f800b6779..1ba1eb62e8439 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts @@ -6,8 +6,8 @@ import { APICaller } from 'src/core/server'; import { UMElasticsearchQueryFn } from '../adapters'; -import { IndexPatternsFetcher, IIndexPattern } from '../../../../../../../src/plugins/data/server'; -import { INDEX_NAMES } from '../../../common/constants'; +import { IndexPatternsFetcher, IIndexPattern } from '../../../../../../src/plugins/data/server'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export const getUptimeIndexPattern: UMElasticsearchQueryFn = async callES => { const indexPatternsFetcher = new IndexPatternsFetcher((...rest: Parameters) => diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_index_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts similarity index 76% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_index_status.ts rename to x-pack/plugins/uptime/server/lib/requests/get_index_status.ts index e801b05d057f4..95aa7eeef88e1 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_index_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts @@ -5,8 +5,8 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { StatesIndexStatus } from '../../../common/graphql/types'; -import { INDEX_NAMES } from '../../../common/constants'; +import { StatesIndexStatus } from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export const getIndexStatus: UMElasticsearchQueryFn<{}, StatesIndexStatus> = async ({ callES }) => { const { diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts similarity index 91% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_latest_monitor.ts rename to x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts index bfaee3f2bf7ee..2d549fce06884 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_latest_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts @@ -5,8 +5,8 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { Ping } from '../../../common/graphql/types'; -import { INDEX_NAMES } from '../../../common/constants'; +import { Ping } from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export interface GetLatestMonitorParams { /** @member dateRangeStart timestamp bounds */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor.ts similarity index 87% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor.ts rename to x-pack/plugins/uptime/server/lib/requests/get_monitor.ts index 94175616f374e..20103042f19ab 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor.ts @@ -5,8 +5,8 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { Ping } from '../../../common/graphql/types'; -import { INDEX_NAMES } from '../../../common/constants'; +import { Ping } from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export interface GetMonitorParams { /** @member monitorId optional limit to monitorId */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_charts.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_charts.ts similarity index 96% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_charts.ts rename to x-pack/plugins/uptime/server/lib/requests/get_monitor_charts.ts index b97cc7287e921..7dd17ef9aa80f 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_charts.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_charts.ts @@ -5,9 +5,12 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { INDEX_NAMES } from '../../../common/constants'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; import { getHistogramIntervalFormatted } from '../helper'; -import { MonitorChart, LocationDurationLine } from '../../../common/graphql/types'; +import { + MonitorChart, + LocationDurationLine, +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; export interface GetMonitorChartsParams { /** @member monitorId ID value for the selected monitor */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_details.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts similarity index 88% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_details.ts rename to x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts index b516fde1ce844..eb3657e60a7bb 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_details.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_details.ts @@ -5,8 +5,11 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { MonitorDetails, MonitorError } from '../../../common/runtime_types'; -import { INDEX_NAMES } from '../../../common/constants'; +import { + MonitorDetails, + MonitorError, +} from '../../../../../legacy/plugins/uptime/common/runtime_types'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export interface GetMonitorDetailsParams { monitorId: string; diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_locations.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts similarity index 92% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_locations.ts rename to x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts index e1a0e14fe951d..328ef54c404d3 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_locations.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts @@ -5,8 +5,14 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { INDEX_NAMES, UNNAMED_LOCATION } from '../../../common/constants'; -import { MonitorLocations, MonitorLocation } from '../../../common/runtime_types'; +import { + INDEX_NAMES, + UNNAMED_LOCATION, +} from '../../../../../legacy/plugins/uptime/common/constants'; +import { + MonitorLocations, + MonitorLocation, +} from '../../../../../legacy/plugins/uptime/common/runtime_types'; /** * Fetch data for the monitor page title. diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_states.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts similarity index 89% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_states.ts rename to x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts index 32c82b1fa2098..5b02e2502a27e 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_monitor_states.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CONTEXT_DEFAULTS } from '../../../common/constants'; +import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants'; import { fetchPage } from './search'; import { UMElasticsearchQueryFn } from '../adapters'; -import { MonitorSummary, SortOrder, CursorDirection } from '../../../common/graphql/types'; +import { + MonitorSummary, + SortOrder, + CursorDirection, +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { QueryContext } from './search'; export interface CursorPagination { diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_ping_histogram.ts b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts similarity index 93% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_ping_histogram.ts rename to x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts index 7b8ca4708255c..339409b63a4f6 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts @@ -5,10 +5,10 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; +import { INDEX_NAMES, QUERY } from '../../../../../legacy/plugins/uptime/common/constants'; import { getFilterClause } from '../helper'; -import { INDEX_NAMES, QUERY } from '../../../common/constants'; import { HistogramQueryResult } from './types'; -import { HistogramResult } from '../../../common/types'; +import { HistogramResult } from '../../../../../legacy/plugins/uptime/common/types'; export interface GetPingHistogramParams { /** @member dateRangeStart timestamp bounds */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_pings.ts b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts similarity index 93% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_pings.ts rename to x-pack/plugins/uptime/server/lib/requests/get_pings.ts index 381aca720dc1d..ddca27d782066 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_pings.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts @@ -5,8 +5,12 @@ */ import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { PingResults, Ping, HttpBody } from '../../../common/graphql/types'; -import { INDEX_NAMES } from '../../../common/constants'; +import { + PingResults, + Ping, + HttpBody, +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; export interface GetPingsParams { /** @member dateRangeStart timestamp bounds */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/get_snapshot_counts.ts b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts similarity index 96% rename from x-pack/legacy/plugins/uptime/server/lib/requests/get_snapshot_counts.ts rename to x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts index 8d84c0f4d6769..050e906f01c62 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/get_snapshot_counts.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts @@ -5,9 +5,12 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { Snapshot } from '../../../common/runtime_types'; +import { Snapshot } from '../../../../../legacy/plugins/uptime/common/runtime_types'; +import { + CONTEXT_DEFAULTS, + INDEX_NAMES, +} from '../../../../../legacy/plugins/uptime/common/constants'; import { QueryContext } from './search'; -import { CONTEXT_DEFAULTS, INDEX_NAMES } from '../../../common/constants'; export interface GetSnapshotCountParams { dateRangeStart: string; diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/index.ts b/x-pack/plugins/uptime/server/lib/requests/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/index.ts rename to x-pack/plugins/uptime/server/lib/requests/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/fetch_page.test.ts b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/fetch_page.test.ts similarity index 96% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/fetch_page.test.ts rename to x-pack/plugins/uptime/server/lib/requests/search/__tests__/fetch_page.test.ts index d519a4e75463f..f542773f32796 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/fetch_page.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/fetch_page.test.ts @@ -12,7 +12,7 @@ import { MonitorGroupsPage, } from '../fetch_page'; import { QueryContext } from '../query_context'; -import { MonitorSummary } from '../../../../../common/graphql/types'; +import { MonitorSummary } from '../../../../../../../legacy/plugins/uptime/common/graphql/types'; import { nextPagination, prevPagination, simpleQueryContext } from './test_helpers'; const simpleFixture: MonitorGroups[] = [ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/monitor_group_iterator.test.ts b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/monitor_group_iterator.test.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/monitor_group_iterator.test.ts rename to x-pack/plugins/uptime/server/lib/requests/search/__tests__/monitor_group_iterator.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/query_context.test.ts b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/query_context.test.ts similarity index 95% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/query_context.test.ts rename to x-pack/plugins/uptime/server/lib/requests/search/__tests__/query_context.test.ts index 86506c2c4c044..ea81ec623e01c 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/query_context.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/query_context.test.ts @@ -6,7 +6,10 @@ import { QueryContext } from '../query_context'; import { CursorPagination } from '../types'; -import { CursorDirection, SortOrder } from '../../../../../common/graphql/types'; +import { + CursorDirection, + SortOrder, +} from '../../../../../../../legacy/plugins/uptime/common/graphql/types'; describe(QueryContext, () => { // 10 minute range diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/test_helpers.ts b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/test_helpers.ts similarity index 88% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/test_helpers.ts rename to x-pack/plugins/uptime/server/lib/requests/search/__tests__/test_helpers.ts index 98b192d14f91a..d96f8dc95aa72 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/__tests__/test_helpers.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/__tests__/test_helpers.ts @@ -5,7 +5,10 @@ */ import { CursorPagination } from '../types'; -import { CursorDirection, SortOrder } from '../../../../../common/graphql/types'; +import { + CursorDirection, + SortOrder, +} from '../../../../../../../legacy/plugins/uptime/common/graphql/types'; import { QueryContext } from '../query_context'; export const prevPagination = (key: any): CursorPagination => { diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts similarity index 98% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts rename to x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts index e37c749e63566..9ad3928a3b1b2 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts @@ -7,14 +7,14 @@ import { get, sortBy } from 'lodash'; import { QueryContext } from './query_context'; import { getHistogramIntervalFormatted } from '../../helper'; -import { INDEX_NAMES, STATES } from '../../../../common/constants'; +import { INDEX_NAMES, STATES } from '../../../../../../legacy/plugins/uptime/common/constants'; import { MonitorSummary, SummaryHistogram, Check, CursorDirection, SortOrder, -} from '../../../../common/graphql/types'; +} from '../../../../../../legacy/plugins/uptime/common/graphql/types'; import { MonitorEnricher } from './fetch_page'; export const enrichMonitorGroups: MonitorEnricher = async ( diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/fetch_chunk.ts b/x-pack/plugins/uptime/server/lib/requests/search/fetch_chunk.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/fetch_chunk.ts rename to x-pack/plugins/uptime/server/lib/requests/search/fetch_chunk.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/fetch_page.ts b/x-pack/plugins/uptime/server/lib/requests/search/fetch_page.ts similarity index 96% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/fetch_page.ts rename to x-pack/plugins/uptime/server/lib/requests/search/fetch_page.ts index 6440850dc0ffc..62144dacbd377 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/fetch_page.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/fetch_page.ts @@ -7,8 +7,12 @@ import { flatten } from 'lodash'; import { CursorPagination } from './types'; import { QueryContext } from './query_context'; -import { QUERY } from '../../../../common/constants'; -import { CursorDirection, MonitorSummary, SortOrder } from '../../../../common/graphql/types'; +import { QUERY } from '../../../../../../legacy/plugins/uptime/common/constants'; +import { + CursorDirection, + MonitorSummary, + SortOrder, +} from '../../../../../../legacy/plugins/uptime/common/graphql/types'; import { enrichMonitorGroups } from './enrich_monitor_groups'; import { MonitorGroupIterator } from './monitor_group_iterator'; diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/find_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts similarity index 95% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/find_potential_matches.ts rename to x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts index fc0e35b279e0b..9b3b1186472be 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/find_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts @@ -5,8 +5,8 @@ */ import { get, set } from 'lodash'; -import { CursorDirection } from '../../../../common/graphql/types'; -import { INDEX_NAMES } from '../../../../common/constants'; +import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/graphql/types'; +import { INDEX_NAMES } from '../../../../../../legacy/plugins/uptime/common/constants'; import { QueryContext } from './query_context'; // This is the first phase of the query. In it, we find the most recent check groups that matched the given query. diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/index.ts b/x-pack/plugins/uptime/server/lib/requests/search/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/index.ts rename to x-pack/plugins/uptime/server/lib/requests/search/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts b/x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts similarity index 98% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts rename to x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts index ced557dbf62e0..267551907c5e8 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts @@ -6,7 +6,7 @@ import { QueryContext } from './query_context'; import { fetchChunk } from './fetch_chunk'; -import { CursorDirection } from '../../../../common/graphql/types'; +import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/graphql/types'; import { MonitorGroups } from './fetch_page'; import { CursorPagination } from './types'; diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/query_context.ts b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts similarity index 97% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/query_context.ts rename to x-pack/plugins/uptime/server/lib/requests/search/query_context.ts index f5b13c165d87d..c1f5d89ec1a38 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/query_context.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts @@ -6,7 +6,7 @@ import moment from 'moment'; import { APICaller } from 'src/core/server'; -import { INDEX_NAMES } from '../../../../common/constants'; +import { INDEX_NAMES } from '../../../../../../legacy/plugins/uptime/common/constants'; import { CursorPagination } from './types'; import { parseRelativeDate } from '../../helper'; diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts similarity index 96% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts rename to x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts index a55301555c8bf..c55aff3e8c4cd 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { INDEX_NAMES } from '../../../../common/constants'; +import { INDEX_NAMES } from '../../../../../../legacy/plugins/uptime/common/constants'; import { QueryContext } from './query_context'; -import { CursorDirection } from '../../../../common/graphql/types'; +import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/graphql/types'; import { MonitorGroups, MonitorLocCheckGroup } from './fetch_page'; /** diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/search/types.ts b/x-pack/plugins/uptime/server/lib/requests/search/types.ts similarity index 76% rename from x-pack/legacy/plugins/uptime/server/lib/requests/search/types.ts rename to x-pack/plugins/uptime/server/lib/requests/search/types.ts index dc6021a91146a..42c98ace6e8f5 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/search/types.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/types.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CursorDirection, SortOrder } from '../../../../common/graphql/types'; +import { + CursorDirection, + SortOrder, +} from '../../../../../../legacy/plugins/uptime/common/graphql/types'; export interface CursorPagination { cursorKey?: any; diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/types.ts b/x-pack/plugins/uptime/server/lib/requests/types.ts similarity index 89% rename from x-pack/legacy/plugins/uptime/server/lib/requests/types.ts rename to x-pack/plugins/uptime/server/lib/requests/types.ts index e17eb546712a9..53a4e989e3789 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/types.ts +++ b/x-pack/plugins/uptime/server/lib/requests/types.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Ping, PingResults } from '../../../common/graphql/types'; +import { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { UMElasticsearchQueryFn } from '../adapters'; -import { GetPingHistogramParams, HistogramResult } from '../../../common/types'; +import { + GetPingHistogramParams, + HistogramResult, +} from '../../../../../legacy/plugins/uptime/common/types'; export interface GetAllParams { /** @member dateRangeStart timestamp bounds */ diff --git a/x-pack/legacy/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts similarity index 83% rename from x-pack/legacy/plugins/uptime/server/lib/requests/uptime_requests.ts rename to x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 73be850306202..8a411368c228f 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -5,7 +5,12 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { Ping, MonitorChart, PingResults, StatesIndexStatus } from '../../../common/graphql/types'; +import { + Ping, + MonitorChart, + PingResults, + StatesIndexStatus, +} from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { GetFilterBarParams, GetLatestMonitorParams, @@ -22,10 +27,10 @@ import { MonitorDetails, MonitorLocations, Snapshot, -} from '../../../common/runtime_types'; +} from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { GetMonitorStatesResult } from './get_monitor_states'; import { GetSnapshotCountParams } from './get_snapshot_counts'; -import { HistogramResult } from '../../../common/types'; +import { HistogramResult } from '../../../../../legacy/plugins/uptime/common/types'; type ESQ = UMElasticsearchQueryFn; diff --git a/x-pack/plugins/uptime/server/plugin.ts b/x-pack/plugins/uptime/server/plugin.ts new file mode 100644 index 0000000000000..e217b0e2f1ad8 --- /dev/null +++ b/x-pack/plugins/uptime/server/plugin.ts @@ -0,0 +1,17 @@ +/* + * 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 { PluginInitializerContext, CoreStart, CoreSetup } from '../../../../src/core/server'; +import { initServerWithKibana } from './kibana.index'; +import { UptimeCorePlugins } from './lib/adapters'; + +export class Plugin { + constructor(_initializerContext: PluginInitializerContext) {} + public setup(core: CoreSetup, plugins: UptimeCorePlugins) { + initServerWithKibana({ route: core.http.createRouter() }, plugins); + } + public start(_core: CoreStart, _plugins: any) {} +} diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/create_route_with_auth.ts b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/create_route_with_auth.ts rename to x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/index.ts rename to x-pack/plugins/uptime/server/rest_api/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/index_pattern/get_index_pattern.ts b/x-pack/plugins/uptime/server/rest_api/index_pattern/get_index_pattern.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/index_pattern/get_index_pattern.ts rename to x-pack/plugins/uptime/server/rest_api/index_pattern/get_index_pattern.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/index_pattern/index.ts b/x-pack/plugins/uptime/server/rest_api/index_pattern/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/index_pattern/index.ts rename to x-pack/plugins/uptime/server/rest_api/index_pattern/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/index.ts b/x-pack/plugins/uptime/server/rest_api/monitors/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/monitors/index.ts rename to x-pack/plugins/uptime/server/rest_api/monitors/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitor_locations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitor_locations.ts rename to x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts rename to x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/status.ts b/x-pack/plugins/uptime/server/rest_api/monitors/status.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/monitors/status.ts rename to x-pack/plugins/uptime/server/rest_api/monitors/status.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts rename to x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/index.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/index.ts rename to x-pack/plugins/uptime/server/rest_api/overview_filters/index.ts diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts new file mode 100644 index 0000000000000..21168edfc9744 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_all.ts @@ -0,0 +1,48 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { UMServerLibs } from '../../lib/lib'; +import { UMRestApiRouteFactory } from '../types'; + +export const createGetAllRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/pings', + validate: { + query: schema.object({ + dateRangeStart: schema.string(), + dateRangeEnd: schema.string(), + location: schema.maybe(schema.string()), + monitorId: schema.maybe(schema.string()), + size: schema.maybe(schema.number()), + sort: schema.maybe(schema.string()), + status: schema.maybe(schema.string()), + }), + }, + options: { + tags: ['access:uptime'], + }, + handler: async ({ callES }, _context, request, response): Promise => { + const { dateRangeStart, dateRangeEnd, location, monitorId, size, sort, status } = request.query; + + const result = await libs.requests.getPings({ + callES, + dateRangeStart, + dateRangeEnd, + monitorId, + status, + sort, + size, + location, + }); + + return response.ok({ + body: { + ...result, + }, + }); + }, +}); diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts rename to x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/pings/get_pings.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/pings/get_pings.ts rename to x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/pings/index.ts b/x-pack/plugins/uptime/server/rest_api/pings/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/pings/index.ts rename to x-pack/plugins/uptime/server/rest_api/pings/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts rename to x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/index.ts b/x-pack/plugins/uptime/server/rest_api/snapshot/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/snapshot/index.ts rename to x-pack/plugins/uptime/server/rest_api/snapshot/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/telemetry/index.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/index.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/telemetry/index.ts rename to x-pack/plugins/uptime/server/rest_api/telemetry/index.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/telemetry/log_monitor_page.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_monitor_page.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/telemetry/log_monitor_page.ts rename to x-pack/plugins/uptime/server/rest_api/telemetry/log_monitor_page.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/telemetry/log_overview_page.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_overview_page.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/telemetry/log_overview_page.ts rename to x-pack/plugins/uptime/server/rest_api/telemetry/log_overview_page.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/types.ts rename to x-pack/plugins/uptime/server/rest_api/types.ts diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/rest_api/uptime_route_wrapper.ts rename to x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts diff --git a/x-pack/legacy/plugins/uptime/server/uptime_server.ts b/x-pack/plugins/uptime/server/uptime_server.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/server/uptime_server.ts rename to x-pack/plugins/uptime/server/uptime_server.ts diff --git a/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts b/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts index f89905f0da04f..4d3167b14b86f 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts @@ -13,7 +13,8 @@ export const makePing = async ( es: any, monitorId: string, fields: { [key: string]: any }, - mogrify: (doc: any) => any + mogrify: (doc: any) => any, + refresh: boolean = true ) => { const baseDoc = { tcp: { @@ -103,7 +104,7 @@ export const makePing = async ( await es.index({ index: INDEX_NAME, - refresh: true, + refresh, body: doc, }); @@ -115,7 +116,8 @@ export const makeCheck = async ( monitorId: string, numIps: number, fields: { [key: string]: any }, - mogrify: (doc: any) => any + mogrify: (doc: any) => any, + refresh: boolean = true ) => { const cgFields = { monitor: { @@ -137,11 +139,16 @@ export const makeCheck = async ( if (i === numIps - 1) { pingFields.summary = summary; } - const doc = await makePing(es, monitorId, pingFields, mogrify); + const doc = await makePing(es, monitorId, pingFields, mogrify, false); docs.push(doc); // @ts-ignore summary[doc.monitor.status]++; } + + if (refresh) { + es.indices.refresh(); + } + return docs; }; @@ -152,7 +159,8 @@ export const makeChecks = async ( numIps: number, every: number, // number of millis between checks fields: { [key: string]: any } = {}, - mogrify: (doc: any) => any = d => d + mogrify: (doc: any) => any = d => d, + refresh: boolean = true ) => { const checks = []; const oldestTime = new Date().getTime() - numChecks * every; @@ -169,7 +177,11 @@ export const makeChecks = async ( }, }, }); - checks.push(await makeCheck(es, monitorId, numIps, fields, mogrify)); + checks.push(await makeCheck(es, monitorId, numIps, fields, mogrify, false)); + } + + if (refresh) { + es.indices.refresh(); } return checks; @@ -183,19 +195,29 @@ export const makeChecksWithStatus = async ( every: number, fields: { [key: string]: any } = {}, status: 'up' | 'down', - mogrify: (doc: any) => any = d => d + mogrify: (doc: any) => any = d => d, + refresh: boolean = true ) => { const oppositeStatus = status === 'up' ? 'down' : 'up'; - return await makeChecks(es, monitorId, numChecks, numIps, every, fields, d => { - d.monitor.status = status; - if (d.summary) { - d.summary[status] += d.summary[oppositeStatus]; - d.summary[oppositeStatus] = 0; - } - - return mogrify(d); - }); + return await makeChecks( + es, + monitorId, + numChecks, + numIps, + every, + fields, + d => { + d.monitor.status = status; + if (d.summary) { + d.summary[status] += d.summary[oppositeStatus]; + d.summary[oppositeStatus] = 0; + } + + return mogrify(d); + }, + refresh + ); }; // Helper for processing a list of checks to find the time picker bounds. diff --git a/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts b/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts index 429f50ec0aa5b..0982d5fef7cb4 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/ping_histogram.ts @@ -6,7 +6,7 @@ import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { assertCloseTo } from '../../../../../legacy/plugins/uptime/server/lib/helper'; +import { assertCloseTo } from '../../../../../plugins/uptime/server/lib/helper'; export default function({ getService }: FtrProviderContext) { describe('pingHistogram', () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts index 79a1e667e5458..a1cb60483c332 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts @@ -74,6 +74,17 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + it('should report that it failed to import a thousand and one (10001) simple rules', async () => { + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .attach('file', getSimpleRuleAsNdjson(new Array(10001).fill('rule-1')), 'rules.ndjson') + .query() + .expect(500); + + expect(body).to.eql({ message: "Can't import more than 10000 rules", status_code: 500 }); + }); + it('should be able to read an imported rule back out correctly', async () => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts index 798a04cae3740..1bcdeef394c00 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts @@ -36,7 +36,7 @@ export default function({ getService }: FtrProviderContext) { }, dependentVariable: 'y', trainingPercent: '20', - modelMemory: '105mb', + modelMemory: '200mb', createIndexPattern: true, expected: { row: { diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js index 9fc8078162847..38fc1f0c6356f 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js @@ -6,10 +6,7 @@ import expect from '@kbn/expect'; -import { - ReindexStatus, - REINDEX_OP_TYPE, -} from '../../../legacy/plugins/upgrade_assistant/common/types'; +import { ReindexStatus, REINDEX_OP_TYPE } from '../../../plugins/upgrade_assistant/common/types'; export default function({ getService }) { const supertest = getService('supertest'); diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 978271166cc05..723da7cef6a77 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -36,7 +36,7 @@ "x-pack/test_utils/*" ], "plugins/*": ["src/legacy/core_plugins/*/public/"], - "fixtures/*": ["src/fixtures/*"] + "fixtures/*": ["src/fixtures/*"], }, "types": [ "node", diff --git a/yarn.lock b/yarn.lock index f46e869909e2e..7906f363813b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5684,6 +5684,13 @@ text-table "^0.2.0" webpack-log "^1.1.2" +"@welldone-software/why-did-you-render@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-4.0.0.tgz#cc98c996f5a06ea55bd07dc99ba4b4d68af93332" + integrity sha512-PjqriZ8Ak9biP2+kOcIrg+NwsFwWVhGV03Hm+ns84YBCArn+hWBKM9rMBEU6e62I1qyrYF2/G9yktNpEmfWfJA== + dependencies: + lodash "^4" + "@wry/context@^0.4.0": version "0.4.1" resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.4.1.tgz#b3e23ca036035cbad0bd9711269352dd03a6fe3c" @@ -19767,7 +19774,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==