From 1530029c359f01da643749a1d0db11a5cd3578d4 Mon Sep 17 00:00:00 2001 From: Artyom Gospodarsky Date: Mon, 9 Dec 2019 14:31:49 +0300 Subject: [PATCH 01/36] Cleanup: Replace IndexPatterns types (#52188) * IndexPatternsContract * types * Expose FieldList on indexPattern contract (as its stateful) Rename FieldListInterface to IFieldList Remove more exports from ui/index_patterns Fix Maps import Move data services initialization to top level of the plugin * Clean up mocks * ts * setFieldFormats from Legacy plugin * Remove FIeldList export * Fix checking of error type in management * Fix security import of indexpatterns * fix jest tests * Fix jest tests * Update snapshots * Fix mocha tests --- .../filter/action/apply_filter_action.ts | 6 +- src/legacy/core_plugins/data/public/index.ts | 12 +--- .../data/public/index_patterns/index.ts | 10 +-- src/legacy/core_plugins/data/public/plugin.ts | 49 +++---------- .../data/public/search/expressions/esaggs.ts | 4 +- .../data/public/shim/legacy_module.ts | 4 +- .../public/components/editor/controls_tab.js | 4 +- .../public/control/list_control_factory.js | 3 +- .../control/list_control_factory.test.js | 23 +++--- .../public/control/range_control_factory.js | 3 +- .../control/range_control_factory.test.js | 23 +++--- .../kibana/public/dashboard/application.ts | 3 - .../kibana/public/dashboard/dashboard_app.tsx | 1 + .../dashboard/dashboard_app_controller.tsx | 4 +- .../kibana/public/dashboard/legacy_app.js | 6 +- .../kibana/public/dashboard/plugin.ts | 5 -- .../discover/angular/context/api/context.ts | 6 +- .../public/discover/angular/discover.js | 23 +++--- .../table_header/table_header.test.tsx | 6 +- .../public/discover/components/doc/doc.tsx | 5 +- .../components/field_chooser/field_chooser.js | 2 +- .../public/discover/get_inner_angular.ts | 2 +- .../public/discover/helpers/build_services.ts | 9 +-- .../kibana/public/discover/kibana_services.ts | 8 ++- .../core_plugins/kibana/public/home/index.ts | 3 +- .../core_plugins/kibana/public/home/plugin.ts | 8 +-- .../objects/lib/resolve_saved_objects.js | 6 +- .../kibana/public/visualize/editor/editor.js | 5 +- .../kibana/public/visualize/index.js | 4 +- .../public/table_vis_controller.test.ts | 2 +- .../public/vega_visualization.js | 3 +- .../expressions/visualization_function.ts | 4 +- .../ui/public/agg_types/param_types/field.ts | 4 +- .../public/index_patterns/__mocks__/index.ts | 9 --- src/legacy/ui/public/index_patterns/index.ts | 15 +--- .../ensure_default_index_pattern.tsx | 6 +- .../ui/public/vis/__tests__/_agg_config.js | 4 +- .../public/index_patterns/fields/field.ts | 2 +- .../index_patterns/fields/field_list.ts | 4 +- .../data/public/index_patterns/index.ts | 8 +-- .../index_patterns/index_pattern.test.ts | 2 +- .../index_patterns/index_pattern.ts | 6 +- .../index_patterns/index_patterns.ts | 2 + .../index_patterns_service.mock.ts | 58 --------------- .../index_patterns/index_patterns_service.ts | 70 ------------------- .../data/public/index_patterns/types.ts | 23 ------ src/plugins/data/public/mocks.ts | 12 ++-- src/plugins/data/public/plugin.ts | 17 ++--- .../public/{index_patterns => }/services.ts | 4 +- src/plugins/data/public/types.ts | 5 +- .../query_string_input.test.tsx.snap | 48 ++----------- src/test_utils/public/stub_index_pattern.js | 26 ++++--- .../public/lib/adapters/elasticsearch/rest.ts | 3 +- .../file_upload/public/kibana_services.js | 4 +- x-pack/legacy/plugins/graph/public/index.ts | 2 - x-pack/legacy/plugins/graph/public/plugin.ts | 8 +-- .../legacy/plugins/graph/public/render_app.ts | 8 ++- .../lens/public/app_plugin/app.test.tsx | 12 ++-- .../plugins/lens/public/app_plugin/app.tsx | 21 +++--- .../plugins/lens/public/app_plugin/plugin.tsx | 3 +- .../embeddable/embeddable_factory.ts | 6 +- .../public/editor_frame_plugin/plugin.tsx | 16 ++--- .../plugins/maps/public/kibana_services.js | 4 +- .../kibana/__mocks__/index_patterns.ts | 4 +- .../contexts/kibana/kibana_context.ts | 7 +- .../pages/analytics_exploration/directive.tsx | 4 +- .../pages/analytics_management/directive.tsx | 4 +- .../file_based/file_datavisualizer.tsx | 4 +- .../file_datavisualizer_directive.tsx | 4 +- .../datavisualizer/index_based/directive.tsx | 4 +- .../jobs/new_job/pages/job_type/directive.tsx | 4 +- .../jobs/new_job/pages/new_job/directive.tsx | 6 +- .../jobs/new_job/recognize/directive.tsx | 4 +- .../services/new_job_capabilities_service.ts | 9 ++- .../ml/public/application/util/index_utils.ts | 13 ++-- .../views/management/edit_role/index.js | 5 +- .../transform/public/app/lib/kibana/common.ts | 11 ++- .../public/app/lib/kibana/kibana_context.tsx | 13 ++-- .../public/app/lib/kibana/kibana_provider.tsx | 4 +- 79 files changed, 248 insertions(+), 520 deletions(-) delete mode 100644 src/plugins/data/public/index_patterns/index_patterns_service.mock.ts delete mode 100644 src/plugins/data/public/index_patterns/index_patterns_service.ts delete mode 100644 src/plugins/data/public/index_patterns/types.ts rename src/plugins/data/public/{index_patterns => }/services.ts (89%) diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index c65ae3a0ec7b93..825b51af96b95d 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -32,7 +32,7 @@ import { applyFiltersPopover, changeTimeFilter, extractTimeFilter, - IndexPatternsStart, + IndexPatternsContract, } from '../../../../../../plugins/data/public'; export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION'; @@ -50,7 +50,7 @@ export function createFilterAction( overlays: CoreStart['overlays'], filterManager: FilterManager, timeFilter: TimefilterContract, - indexPatternsService: IndexPatternsStart + indexPatternsService: IndexPatternsContract ): IAction { return createAction({ type: GLOBAL_APPLY_FILTER_ACTION, @@ -75,7 +75,7 @@ export function createFilterAction( if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( filters.map(filter => { - return indexPatternsService.indexPatterns.get(filter.meta.index!); + return indexPatternsService.get(filter.meta.index!); }) ); diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 9fdc41dfd58e5c..481349463fc566 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -18,7 +18,7 @@ */ // /// Define plugin function -import { DataPlugin as Plugin, DataSetup, DataStart } from './plugin'; +import { DataPlugin as Plugin, DataStart } from './plugin'; export function plugin() { return new Plugin(); @@ -27,15 +27,9 @@ export function plugin() { // /// Export types & static code /** @public types */ -export { DataSetup, DataStart }; +export { DataStart }; -export { - Field, - FieldType, - FieldListInterface, - IndexPattern, - IndexPatterns, -} from './index_patterns'; +export { Field, FieldType, IFieldList, IndexPattern } from './index_patterns'; export { SearchBar, SearchBarProps } from './search'; export { SavedQueryAttributes, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index.ts b/src/legacy/core_plugins/data/public/index_patterns/index.ts index 74981165f3e474..96995c10e54695 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IFieldType, IIndexPattern, indexPatterns } from '../../../../../plugins/data/public'; +import { IFieldType, indexPatterns } from '../../../../../plugins/data/public'; const getFromSavedObject = indexPatterns.getFromSavedObject; const getRoutes = indexPatterns.getRoutes; @@ -25,13 +25,9 @@ const flattenHitWrapper = indexPatterns.flattenHitWrapper; export { getFromSavedObject, getRoutes, flattenHitWrapper }; export { IFieldType as FieldType }; -export { IIndexPattern as StaticIndexPattern }; export { Field, - FieldListInterface, + IFieldList, IndexPattern, - IndexPatterns, - IndexPatternsStart, - IndexPatternsSetup, - IndexPatternsService, + IndexPatternsContract, } from '../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 6cce91a5a25b5e..2a7bd5476f0a5c 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -19,7 +19,6 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { createSearchBar, StatetfulSearchBarProps } from './search'; -import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns'; import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; import { initLegacyModule } from './shim/legacy_module'; @@ -30,27 +29,20 @@ import { } from './filter/action/apply_filter_action'; import { APPLY_FILTER_TRIGGER } from '../../../../plugins/embeddable/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { setFieldFormats } from '../../../../plugins/data/public/services'; + export interface DataPluginStartDependencies { data: DataPublicPluginStart; uiActions: IUiActionsSetup; } -/** - * Interface for this plugin's returned `setup` contract. - * - * @public - */ -export interface DataSetup { - indexPatterns: IndexPatternsSetup; -} - /** * Interface for this plugin's returned `start` contract. * * @public */ export interface DataStart { - indexPatterns: IndexPatternsStart; ui: { SearchBar: React.ComponentType; }; @@ -68,34 +60,17 @@ export interface DataStart { * or static code. */ -export class DataPlugin implements Plugin { - private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); - - private setupApi!: DataSetup; +export class DataPlugin implements Plugin { private storage!: IStorageWrapper; - public setup(core: CoreSetup): DataSetup { + public setup(core: CoreSetup) { this.storage = new Storage(window.localStorage); - - this.setupApi = { - indexPatterns: this.indexPatterns.setup(), - }; - - return this.setupApi; } public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { - const { uiSettings, http, notifications, savedObjects } = core; - - const indexPatternsService = this.indexPatterns.start({ - uiSettings, - savedObjectsClient: savedObjects.client, - http, - notifications, - fieldFormats: data.fieldFormats, - }); - - initLegacyModule(indexPatternsService.indexPatterns); + // This is required for when Angular code uses Field and FieldList. + setFieldFormats(data.fieldFormats); + initLegacyModule(data.indexPatterns); const SearchBar = createSearchBar({ core, @@ -108,22 +83,18 @@ export class DataPlugin implements Plugin { +export const initLegacyModule = once((indexPatterns: IndexPatternsContract): void => { uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js index 6d4ce31d453cc7..d5f58236027a96 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js @@ -23,7 +23,7 @@ import { ControlEditor } from './control_editor'; import { addControl, moveControl, newControl, removeControl, setControl } from '../../editor_utils'; import { getLineageMap, getParentCandidates } from '../../lineage'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { start as data } from '../../../../../core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { EuiButton, @@ -41,7 +41,7 @@ class ControlsTabUi extends Component { } getIndexPattern = async (indexPatternId) => { - return await data.indexPatterns.indexPatterns.get(indexPatternId); + return await npStart.plugins.data.indexPatterns.get(indexPatternId); } onChange = value => this.props.setValue('controls', value) diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js index 86fe6db9b0778e..e1dadff4a9ae74 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js @@ -28,7 +28,6 @@ import { createSearchSource } from './create_search_source'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; -import { start as data } from '../../../../core_plugins/data/public/legacy'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators @@ -173,7 +172,7 @@ class ListControl extends Control { export async function listControlFactory(controlParams, useTimeFilter, SearchSource) { let indexPattern; try { - indexPattern = await data.indexPatterns.indexPatterns.get(controlParams.indexPattern); + indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); // dynamic options are only allowed on String fields but the setting defaults to true so it could // be enabled for non-string fields (since UI input is hidden for non-string fields). diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js index b40a9f8e6efd40..c5f6de1c530974 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js @@ -40,27 +40,20 @@ jest.mock('ui/new_platform', () => ({ getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } + }, + indexPatterns: { + get: () => ({ + fields: { getByName: name => { + const fields = { myField: { name: 'myField' } }; + return fields[name]; + } } + }), } } }, }, })); -jest.mock('../../../../core_plugins/data/public/legacy', () => ({ - start: { - indexPatterns: { - indexPatterns: { - get: () => ({ - fields: { getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - } } - }), - } - }, - } -})); - chrome.getInjected.mockImplementation((key) => { switch(key) { case 'autocompleteTimeout': return 1000; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js index 2a05a1224aab98..d2b1aff78ac2b8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js @@ -26,7 +26,6 @@ import { import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; import { i18n } from '@kbn/i18n'; -import { start as data } from '../../../../core_plugins/data/public/legacy'; import { npStart } from 'ui/new_platform'; const minMaxAgg = (field) => { @@ -103,7 +102,7 @@ class RangeControl extends Control { export async function rangeControlFactory(controlParams, useTimeFilter, SearchSource) { let indexPattern; try { - indexPattern = await data.indexPatterns.indexPatterns.get(controlParams.indexPattern); + indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); } catch (err) { // ignore not found error and return control so it can be displayed in disabled state. } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js index 3e6d6a49a11184..eaefcda4140fcb 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js @@ -48,27 +48,20 @@ jest.mock('ui/new_platform', () => ({ getAppFilters: jest.fn().mockImplementation(() => ([])), getGlobalFilters: jest.fn().mockImplementation(() => ([])), } + }, + indexPatterns: { + get: () => ({ + fields: { getByName: name => { + const fields = { myNumberField: { name: 'myNumberField' } }; + return fields[name]; + } + } }), } } }, }, })); -jest.mock('../../../../core_plugins/data/public/legacy', () => ({ - start: { - indexPatterns: { - indexPatterns: { - get: () => ({ - fields: { getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - } - } }), - } - }, - } -})); - describe('fetch', () => { const controlParams = { id: '1', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 42ecc0fea5f073..b58325a77e61e4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -47,7 +47,6 @@ import { // @ts-ignore import { initDashboardApp } from './legacy_app'; -import { DataStart, IndexPatterns } from '../../../data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; @@ -55,8 +54,6 @@ import { SharePluginStart } from '../../../../../plugins/share/public'; export interface RenderDeps { core: LegacyCoreStart; - indexPatterns: IndexPatterns; - dataStart: DataStart; npDataStart: NpDataStart; navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index dcedb2a166fcc0..04a8e68276fc20 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -118,6 +118,7 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { AppStateClass: AppState, config, confirmModal, + indexPatterns: deps.npDataStart.indexPatterns, ...deps, }), }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index b5a6db912bdf07..7eac251a532c74 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -39,7 +39,7 @@ import { unhashUrl, } from './legacy_imports'; import { FilterStateManager, IndexPattern } from '../../../data/public'; -import { Query, SavedQuery, IndexPatterns } from '../../../../../plugins/data/public'; +import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; import './dashboard_empty_screen_directive'; @@ -78,7 +78,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $routeParams: any; getAppState: any; globalState: State; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; dashboardConfig: any; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js index c7f2adb4b875b9..7d60b80b59620c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js @@ -119,7 +119,7 @@ export function initDashboardApp(app, deps) { }, resolve: { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl).then(() => { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -154,7 +154,7 @@ export function initDashboardApp(app, deps) { requireUICapability: 'dashboard.createNew', resolve: { dash: function (redirectWhenMissing, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(); }) @@ -174,7 +174,7 @@ export function initDashboardApp(app, deps) { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { const id = $route.current.params.id; - return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(id); }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index cb0980c914983f..d2b45fde4727e4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -64,7 +64,6 @@ export interface DashboardPluginSetupDependencies { export class DashboardPlugin implements Plugin { private startDependencies: { - dataStart: DataStart; npDataStart: NpDataStart; savedObjectsClient: SavedObjectsClientContract; embeddables: IEmbeddableStart; @@ -84,7 +83,6 @@ export class DashboardPlugin implements Plugin { throw new Error('not started yet'); } const { - dataStart, savedObjectsClient, embeddables, navigation, @@ -96,10 +94,8 @@ export class DashboardPlugin implements Plugin { core: contextCore as LegacyCoreStart, ...angularDependencies, navigation, - dataStart, share, npDataStart, - indexPatterns: dataStart.indexPatterns.indexPatterns, savedObjectsClient, chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, @@ -136,7 +132,6 @@ export class DashboardPlugin implements Plugin { { data: dataStart, embeddables, navigation, npData, share }: DashboardPluginStartDependencies ) { this.startDependencies = { - dataStart, npDataStart: npData, savedObjectsClient, embeddables, diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts index 674f5616faa306..fd71b7c49e8376 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts @@ -17,14 +17,14 @@ * under the License. */ -import { IndexPatterns, IndexPattern, SearchSource } from '../../../kibana_services'; +import { IndexPattern, SearchSource } from '../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; -import { esFilters } from '../../../../../../../../plugins/data/public'; +import { esFilters, IndexPatternsContract } from '../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { @@ -39,7 +39,7 @@ const DAY_MILLIS = 24 * 60 * 60 * 1000; // look from 1 day up to 10000 days into the past and future const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000].map(days => days * DAY_MILLIS); -function fetchContextProvider(indexPatterns: IndexPatterns) { +function fetchContextProvider(indexPatterns: IndexPatternsContract) { return { fetchSurroundingDocs, }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ec0c5c34f7a936..ed233c08e8d492 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -73,7 +73,6 @@ const { } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; -import { start as dataLP } from '../../../../data/public/legacy'; import { generateFilters } from '../../../../../../plugins/data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; import { registerTimefilterWithGlobalStateFactory } from '../../../../../ui/public/timefilter/setup_router'; @@ -123,9 +122,9 @@ app.config($routeProvider => { reloadOnSearch: false, resolve: { savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope) { - const indexPatterns = dataLP.indexPatterns.indexPatterns; + const indexPatterns = getServices().indexPatterns; const savedSearchId = $route.current.params.id; - return ensureDefaultIndexPattern(core, dataLP, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(core, getServices().data, $rootScope, kbnUrl).then(() => { return Promise.props({ ip: indexPatterns.getCache().then((indexPatternList) => { /** @@ -422,7 +421,7 @@ function discoverController( }; const getFieldCounts = async () => { - // the field counts aren't set until we have the dataLP back, + // the field counts aren't set until we have the data back, // so we wait for the fetch to be done before proceeding if ($scope.fetchStatus === fetchStatuses.COMPLETE) { return $scope.fieldCounts; @@ -578,7 +577,7 @@ function discoverController( if (!angular.equals(sort, currentSort)) $scope.fetch(); }); - // update dataLP source when filters update + // update data source when filters update subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { $scope.filters = filterManager.getFilters(); @@ -588,12 +587,12 @@ function discoverController( } })); - // fetch dataLP when filters fire fetch event + // fetch data when filters fire fetch event subscriptions.add(subscribeWithScope($scope, filterManager.getUpdates$(), { next: $scope.fetch })); - // update dataLP source when hitting forward/back and the query changes + // update data source when hitting forward/back and the query changes $scope.$listen($state, 'fetch_with_changes', function (diff) { if (diff.indexOf('query') >= 0) $scope.fetch(); }); @@ -633,7 +632,7 @@ function discoverController( let prev = {}; const status = { UNINITIALIZED: 'uninitialized', - LOADING: 'loading', // initial dataLP load + LOADING: 'loading', // initial data load READY: 'ready', // results came back NO_RESULTS: 'none' // no results came back }; @@ -704,7 +703,7 @@ function discoverController( savedSearchTitle: savedSearch.title, } }), - 'dataLP-test-subj': 'saveSearchSuccess', + 'data-test-subj': 'saveSearchSuccess', }); if (savedSearch.id !== $route.current.params.id) { @@ -765,7 +764,7 @@ function discoverController( } else { toastNotifications.addError(error, { title: i18n.translate('kbn.discover.errorLoadingData', { - defaultMessage: 'Error loading dataLP', + defaultMessage: 'Error loading data', }), }); } @@ -813,10 +812,10 @@ function discoverController( function logInspectorRequest() { inspectorAdapters.requests.reset(); const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { - defaultMessage: 'dataLP', + defaultMessage: 'data', }); const description = i18n.translate('kbn.discover.inspectorRequestDescription', { - defaultMessage: 'This request queries Elasticsearch to fetch the dataLP for the search.', + defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description }); inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx index 09ba77c7c49992..e5706b5e3c9bb9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx @@ -23,7 +23,7 @@ import { TableHeader } from './table_header'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; -import { IndexPattern, FieldType } from '../../../../kibana_services'; +import { IndexPattern, IFieldType } from '../../../../kibana_services'; function getMockIndexPattern() { return ({ @@ -40,7 +40,7 @@ function getMockIndexPattern() { aggregatable: false, searchable: true, sortable: true, - } as FieldType; + } as IFieldType; } else { return { name, @@ -48,7 +48,7 @@ function getMockIndexPattern() { aggregatable: false, searchable: true, sortable: false, - } as FieldType; + } as IFieldType; } }, } as unknown) as IndexPattern; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx index 85308d9c7e03e4..7020addb2bc6d0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/components/doc/doc.tsx @@ -19,9 +19,10 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; +import { IndexPatternsContract } from 'src/plugins/data/public'; import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; -import { IndexPatterns, ElasticSearchHit, getServices } from '../../kibana_services'; +import { ElasticSearchHit, getServices } from '../../kibana_services'; export interface ElasticSearchResult { hits: { @@ -50,7 +51,7 @@ export interface DocProps { /** * IndexPatternService to get a given index pattern by ID */ - indexPatternService: IndexPatterns; + indexPatternService: IndexPatternsContract; /** * Client of ElasticSearch to use for the query */ diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js index cc3d864fd371ea..8674726a81076d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js @@ -23,7 +23,7 @@ import { fieldCalculator } from './lib/field_calculator'; import './discover_field'; import './discover_field_search_directive'; import './discover_index_pattern_directive'; -import { FieldList } from '../../kibana_services'; +import { FieldList } from '../../../../../../../plugins/data/public'; import fieldChooserTemplate from './field_chooser.html'; export function createFieldChooserDirective($location, config, $route) { diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index 8e2d15ae48a1fd..275dfa073fecdf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -301,7 +301,7 @@ function createElasticSearchModule() { } function createIndexPatternsModule() { - angular.module('discoverIndexPatterns', []).service('indexPatterns', IndexPatterns); + angular.module('discoverIndexPatterns', []).value('indexPatterns', IndexPatterns); } function createPagerFactoryModule() { diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts index 51f1521504be8e..a7f849704b5b26 100644 --- a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts @@ -27,7 +27,7 @@ import { import * as docViewsRegistry from 'ui/registry/doc_views'; import chromeLegacy from 'ui/chrome'; import { IPrivate } from 'ui/private'; -import { FilterManager, TimefilterContract } from 'src/plugins/data/public'; +import { FilterManager, TimefilterContract, IndexPatternsContract } from 'src/plugins/data/public'; // @ts-ignore import { StateProvider } from 'ui/state_management/state'; // @ts-ignore @@ -35,8 +35,6 @@ import { createSavedSearchesService } from '../saved_searches/saved_searches'; // @ts-ignore import { createSavedSearchFactory } from '../saved_searches/_saved_search'; import { DiscoverStartPlugins } from '../plugin'; -import { start as legacyData } from '../../../../data/public/legacy'; -import { DataStart, IndexPatterns } from '../../../../data/public'; import { EuiUtilsStart } from '../../../../../../plugins/eui_utils/public'; import { SavedSearch } from '../types'; import { SharePluginStart } from '../../../../../../plugins/share/public'; @@ -46,12 +44,11 @@ export interface DiscoverServices { capabilities: Capabilities; chrome: ChromeStart; core: CoreStart; - data: DataStart; docLinks: DocLinksStart; docViewsRegistry: docViewsRegistry.DocViewsRegistry; eui_utils: EuiUtilsStart; filterManager: FilterManager; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; inspector: unknown; metadata: { branch: string }; share: SharePluginStart; @@ -98,7 +95,7 @@ export async function buildServices(core: CoreStart, plugins: DiscoverStartPlugi docViewsRegistry, eui_utils: plugins.eui_utils, filterManager: plugins.data.query.filterManager, - indexPatterns: legacyData.indexPatterns.indexPatterns, + indexPatterns: plugins.data.indexPatterns, inspector: plugins.inspector, // @ts-ignore metadata: core.injectedMetadata.getLegacyMetadata(), diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 43a0afa83dfe49..d13d0dc868a588 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -67,7 +67,6 @@ export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; // @ts-ignore export { RequestAdapter } from 'ui/inspector/adapters'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { FieldList } from 'ui/index_patterns'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; @@ -82,7 +81,12 @@ export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; // EXPORT types export { Vis } from 'ui/vis'; -export { IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns'; +export { + IndexPatternsContract, + IIndexPattern, + IndexPattern, + IFieldType, +} from '../../../../../plugins/data/public'; export { ElasticSearchHit } from 'ui/registry/doc_views_types'; export { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; export { Adapters } from 'ui/inspector/types'; diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index 6494cc79640e1c..bd3a0b38ec3f0e 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -23,7 +23,6 @@ import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as data } from '../../../data/public/legacy'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; export const trackUiMetric = createUiStatsReporter('Kibana_home'); @@ -74,6 +73,6 @@ let copiedLegacyCatalogue = false; }, }); instance.start(npStart.core, { - data, + data: npStart.plugins.data, }); })(); diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index bb0e7e3611616c..fc1747d71d069e 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -20,7 +20,7 @@ import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; -import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { setServices } from './kibana_services'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; @@ -31,7 +31,7 @@ export interface LegacyAngularInjectedDependencies { } export interface HomePluginStartDependencies { - data: DataStart; + data: DataPublicPluginStart; } export interface HomePluginSetupDependencies { @@ -58,7 +58,7 @@ export interface HomePluginSetupDependencies { } export class HomePlugin implements Plugin { - private dataStart: DataStart | null = null; + private dataStart: DataPublicPluginStart | null = null; private savedObjectsClient: any = null; setup( @@ -85,7 +85,7 @@ export class HomePlugin implements Plugin { uiSettings: core.uiSettings, addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, - indexPatternService: this.dataStart!.indexPatterns.indexPatterns, + indexPatternService: this.dataStart!.indexPatterns, ...angularDependencies, }); const { renderApp } = await import('./render_app'); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js index 162fae33ba19e5..22c3e372230f52 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js @@ -17,7 +17,6 @@ * under the License. */ -import { SavedObjectNotFound } from '../../../../../../../../plugins/kibana_utils/public'; import { i18n } from '@kbn/i18n'; async function getSavedObject(doc, services) { @@ -255,7 +254,8 @@ export async function resolveSavedObjects(savedObjects, overwriteAll, services, importedObjectCount++; } } catch (error) { - if (error instanceof SavedObjectNotFound) { + + if (error.constructor.name === 'SavedObjectNotFound') { if (error.savedObjectType === 'index-pattern') { conflictedIndexPatterns.push({ obj, doc: searchDoc }); } else { @@ -275,7 +275,7 @@ export async function resolveSavedObjects(savedObjects, overwriteAll, services, importedObjectCount++; } } catch (error) { - const isIndexPatternNotFound = error instanceof SavedObjectNotFound && + const isIndexPatternNotFound = error.constructor.name === 'SavedObjectNotFound' && error.savedObjectType === 'index-pattern'; if (isIndexPatternNotFound && obj.savedSearchId) { conflictedSavedObjectsLinkedToSavedSearches.push(obj); diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index c985e1a00655fa..9a02f5943116d9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -54,7 +54,6 @@ const { chrome, chromeLegacy, npData, - data, docTitle, FilterBarQueryFilterProvider, getBasePath, @@ -86,7 +85,7 @@ uiRoutes ); } - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + return ensureDefaultIndexPattern(core, npData, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) .then(savedVis => { if (savedVis.vis.type.setup) { return savedVis.vis.type.setup(savedVis) @@ -105,7 +104,7 @@ uiRoutes k7Breadcrumbs: getEditBreadcrumbs, resolve: { savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(core, npData, $rootScope, kbnUrl) .then(() => savedVisualizations.get($route.current.params.id)) .then((savedVis) => { chrome.recentlyAccessed.add( diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index 57707f63213768..d42c72f67f8158 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -57,7 +57,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().npData, $rootScope, kbnUrl) }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -67,7 +67,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().npData, $rootScope, kbnUrl) }, }); diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index 5ae58204a8cf30..a2f98b8c64e538 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -39,7 +39,7 @@ import { setup as visualizationsSetup } from '../../visualizations/public/np_rea // eslint-disable-next-line import { stubFields } from '../../../../plugins/data/public/stubs'; // eslint-disable-next-line -import { setFieldFormats } from '../../../../plugins/data/public/index_patterns/services'; +import { setFieldFormats } from '../../../../plugins/data/public/services'; interface TableVisScope extends IScope { [key: string]: any; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js index 7aa60bb0cc4696..1a5c4713ef7e5a 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js @@ -22,7 +22,6 @@ import { toastNotifications } from 'ui/notify'; import { VegaView } from './vega_view/vega_view'; import { VegaMapView } from './vega_view/vega_map_view'; import { timefilter } from 'ui/timefilter'; -import { start as data } from '../../../core_plugins/data/public/legacy'; import { npStart } from 'ui/new_platform'; import { findIndexPatternByTitle } from '../../data/public/index_patterns'; @@ -50,7 +49,7 @@ export const createVegaVisualization = ({ serviceSettings }) => class VegaVisual })); } } else { - idxObj = await data.indexPatterns.indexPatterns.getDefault(); + idxObj = await npStart.plugins.data.indexPatterns.getDefault(); if (!idxObj) { throw new Error(i18n.translate('visTypeVega.visualization.unableToFindDefaultIndexErrorMessage', { defaultMessage: 'Unable to find default index', diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts index 7076cdd8e23e38..ddf1b11945422c 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts +++ b/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts @@ -24,7 +24,7 @@ import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { PersistedState } from 'ui/persisted_state'; import { VisResponseValue } from 'src/plugins/visualizations/public'; import { ExpressionFunction, Render } from 'src/plugins/expressions/public'; -import { start as data } from '../../../data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { start as visualizations } from '../np_ready/public/legacy'; interface Arguments { @@ -92,7 +92,7 @@ export const visualization = (): ExpressionFunctionVisualization => ({ async fn(context, args, handlers) { const $injector = await chrome.dangerouslyGetActiveInjector(); const Private = $injector.get('Private') as any; - const { indexPatterns } = data.indexPatterns; + const indexPatterns = npStart.plugins.data.indexPatterns; const queryFilter = Private(FilterBarQueryFilterProvider); const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/ui/public/agg_types/param_types/field.ts index ab60f5553f79b4..4fda86bd1379f3 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.ts +++ b/src/legacy/ui/public/agg_types/param_types/field.ts @@ -25,7 +25,7 @@ import { FieldParamEditor } from '../../vis/editors/default/controls/field'; import { BaseParamType } from './base'; import { toastNotifications } from '../../notify'; import { propFilter } from '../filter'; -import { Field, FieldListInterface } from '../../index_patterns'; +import { Field, IFieldList } from '../../index_patterns'; const filterByType = propFilter('type'); @@ -111,7 +111,7 @@ export class FieldParamType extends BaseParamType { /** * filter the fields to the available ones */ - getAvailableFields = (fields: FieldListInterface) => { + getAvailableFields = (fields: IFieldList) => { const filteredFields = fields.filter((field: Field) => { const { onlyAggregatable, scriptable, filterFieldTypes } = this; diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index 6f3d39299f9707..0cb1cf897b1665 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -17,18 +17,9 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; import { indexPatterns as npIndexPatterns } from '../../../../../plugins/data/public'; -const dataSetup = dataPluginMock.createSetupContract(); - -// mocks for stateful code -export const { indexPatterns } = dataSetup.indexPatterns!; - export const flattenHitWrapper = npIndexPatterns.flattenHitWrapper; -export const formatHitProvider = npIndexPatterns.formatHitProvider; // static code export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; -export { FieldList } from '../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index 237f3137bc9f4b..47f6e697c54e9a 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -27,23 +27,14 @@ import { indexPatterns } from '../../../../plugins/data/public'; // static code -export { getFromSavedObject, getRoutes } from '../../../core_plugins/data/public'; - export const INDEX_PATTERN_ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS; export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE; export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; export const validateIndexPattern = indexPatterns.validate; export const flattenHitWrapper = indexPatterns.flattenHitWrapper; -export const formatHitProvider = indexPatterns.formatHitProvider; +export const getFromSavedObject = indexPatterns.getFromSavedObject; +export const getRoutes = indexPatterns.getRoutes; // types -export { - Field, - FieldType, - FieldListInterface, - IndexPattern, - IndexPatterns, -} from '../../../core_plugins/data/public'; - -export { FieldList } from '../../../../plugins/data/public'; +export { Field, FieldType, IFieldList, IndexPattern } from '../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx index 98e95865d7325a..bb6b9f805b4136 100644 --- a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiCallOut } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; -import { DataStart } from '../../../core_plugins/data/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; let bannerId: string; let timeoutId: NodeJS.Timeout | undefined; @@ -40,11 +40,11 @@ let timeoutId: NodeJS.Timeout | undefined; */ export async function ensureDefaultIndexPattern( newPlatform: CoreStart, - data: DataStart, + data: DataPublicPluginStart, $rootScope: IRootScopeService, kbnUrl: any ) { - const patterns = await data.indexPatterns.indexPatterns.getIds(); + const patterns = await data.indexPatterns.getIds(); let defaultId = newPlatform.uiSettings.get('defaultIndex'); let defined = !!defaultId; const exists = contains(patterns, defaultId); diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js index 9b0398cf8853e1..559124e20c10a2 100644 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js @@ -463,7 +463,7 @@ describe('AggConfig', function () { }); it('returns the field\'s formatter', function () { - expect(vis.aggs.aggs[0].fieldFormatter()).to.be(vis.aggs.aggs[0].getField().format.getConverterFor()); + expect(vis.aggs.aggs[0].fieldFormatter().toString()).to.be(vis.aggs.aggs[0].getField().format.getConverterFor().toString()); }); it('returns the string format if the field does not have a format', function () { @@ -484,7 +484,7 @@ describe('AggConfig', function () { it('returns the html converter if "html" is passed in', function () { const field = indexPattern.fields.getByName('bytes'); - expect(vis.aggs.aggs[0].fieldFormatter('html')).to.be(field.format.getConverterFor('html')); + expect(vis.aggs.aggs[0].fieldFormatter('html').toString()).to.be(field.format.getConverterFor('html').toString()); }); }); }); diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index ae17188de8625f..c8c8ac1ffd3214 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; import { IndexPattern } from '../index_patterns'; -import { getNotifications, getFieldFormats } from '../services'; +import { getNotifications, getFieldFormats } from '../../services'; import { IFieldType, getKbnFieldType, diff --git a/src/plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts index ff6706cec6c344..03214a8c96427a 100644 --- a/src/plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -24,14 +24,14 @@ import { Field, FieldSpec } from './field'; type FieldMap = Map; -export interface FieldListInterface extends Array { +export interface IFieldList extends Array { getByName(name: Field['name']): Field | undefined; getByType(type: Field['type']): Field[]; add(field: FieldSpec): void; remove(field: IFieldType): void; } -export class FieldList extends Array implements FieldListInterface { +export class FieldList extends Array implements IFieldList { private byName: FieldMap = new Map(); private groups: Map = new Map(); private indexPattern: IndexPattern; diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts index e6978e6d3957d1..6f4821c3917212 100644 --- a/src/plugins/data/public/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index.ts @@ -42,7 +42,7 @@ export const indexPatterns = { formatHitProvider, }; -export { IndexPatternsService } from './index_patterns_service'; -export { Field, FieldList, FieldListInterface } from './fields'; -export { IndexPattern, IndexPatterns } from './index_patterns'; -export { IndexPatternsStart, IndexPatternsSetup } from './types'; +export { Field, FieldList, IFieldList } from './fields'; + +// TODO: figure out how to replace IndexPatterns in get_inner_angular. +export { IndexPattern, IndexPatterns, IndexPatternsContract } from './index_patterns'; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index a0a884454a3f00..f56f94fa8c2605 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -26,7 +26,7 @@ import mockLogStashFields from '../../../../../fixtures/logstash_fields'; // @ts-ignore import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; import { Field } from '../fields'; -import { setNotifications, setFieldFormats } from '../services'; +import { setNotifications, setFieldFormats } from '../../services'; // Temporary disable eslint, will be removed after moving to new platform folder // eslint-disable-next-line @kbn/eslint/no-restricted-paths diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 2c93c0aa9dc620..19e465104cf4cd 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -32,12 +32,12 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../. import { findByTitle, getRoutes } from '../utils'; import { indexPatterns } from '../'; -import { Field, FieldList, FieldListInterface } from '../fields'; +import { Field, FieldList, IFieldList } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; -import { getNotifications, getFieldFormats } from '../services'; +import { getNotifications, getFieldFormats } from '../../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; @@ -50,7 +50,7 @@ export class IndexPattern implements IIndexPattern { public type?: string; public fieldFormatMap: any; public typeMeta: any; - public fields: FieldListInterface; + public fields: IFieldList; public timeFieldName: string | undefined; public formatHit: any; public formatField: any; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index d6a8e7b20451d1..6369a2a3dd5f35 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -146,3 +146,5 @@ export class IndexPatterns { return indexPattern.init(); }; } + +export type IndexPatternsContract = PublicMethodsOf; diff --git a/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts b/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts deleted file mode 100644 index d38e9c76430eb7..00000000000000 --- a/src/plugins/data/public/index_patterns/index_patterns_service.mock.ts +++ /dev/null @@ -1,58 +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 { IndexPatternsService } from './index_patterns_service'; -import { flattenHitWrapper } from './index_patterns'; -import { IndexPatternsSetup } from './types'; - -type IndexPatternsServiceClientContract = PublicMethodsOf; - -const createSetupContractMock = () => { - // Legacy mock - must be removed before migrating to new platform. - // Included here because we only want to call `jest.mock` when somebody creates - // the mock for this contract. - jest.mock('ui/chrome'); - - const setupContract: jest.Mocked = { - FieldList: {} as any, - flattenHitWrapper: jest.fn().mockImplementation(flattenHitWrapper), - formatHitProvider: jest.fn(), - indexPatterns: jest.fn() as any, - IndexPatternSelect: jest.fn(), - }; - - return setupContract; -}; - -const createMock = () => { - const mocked: jest.Mocked = { - setup: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - }; - - mocked.setup.mockReturnValue(createSetupContractMock()); - return mocked; -}; - -export const indexPatternsServiceMock = { - create: createMock, - createSetupContract: createSetupContractMock, - createStartContract: createSetupContractMock, -}; diff --git a/src/plugins/data/public/index_patterns/index_patterns_service.ts b/src/plugins/data/public/index_patterns/index_patterns_service.ts deleted file mode 100644 index 43ba5082ec479c..00000000000000 --- a/src/plugins/data/public/index_patterns/index_patterns_service.ts +++ /dev/null @@ -1,70 +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 { - IUiSettingsClient, - SavedObjectsClientContract, - HttpServiceBase, - NotificationsStart, -} from 'src/core/public'; -import { FieldFormatsStart } from '../field_formats_provider'; -import { setNotifications, setFieldFormats } from './services'; -import { IndexPatterns } from './index_patterns'; - -export interface IndexPatternDependencies { - uiSettings: IUiSettingsClient; - savedObjectsClient: SavedObjectsClientContract; - http: HttpServiceBase; - notifications: NotificationsStart; - fieldFormats: FieldFormatsStart; -} - -/** - * Index Patterns Service - * - * @internal - */ -export class IndexPatternsService { - private setupApi: any; - - public setup() { - this.setupApi = {}; - - return this.setupApi; - } - - public start({ - uiSettings, - savedObjectsClient, - http, - notifications, - fieldFormats, - }: IndexPatternDependencies) { - setNotifications(notifications); - setFieldFormats(fieldFormats); - - return { - indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http), - }; - } - - public stop() { - // nothing to do here yet - } -} diff --git a/src/plugins/data/public/index_patterns/types.ts b/src/plugins/data/public/index_patterns/types.ts deleted file mode 100644 index 59e3075bf215d1..00000000000000 --- a/src/plugins/data/public/index_patterns/types.ts +++ /dev/null @@ -1,23 +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 { IndexPatternsService } from './index_patterns_service'; - -export type IndexPatternsSetup = ReturnType; -export type IndexPatternsStart = ReturnType; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 19c21ab9934ef1..058e6c0e2f5c52 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -16,10 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { FieldFormatRegisty, Plugin, FieldFormatsStart, FieldFormatsSetup } from '.'; +import { + FieldFormatRegisty, + Plugin, + FieldFormatsStart, + FieldFormatsSetup, + IndexPatternsContract, +} from '.'; import { searchSetupMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; -import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; @@ -54,7 +59,6 @@ const createSetupContract = (): Setup => { search: searchSetupMock, fieldFormats: fieldFormatsMock as FieldFormatsSetup, query: querySetupMock, - indexPatterns: indexPatternsServiceMock.createSetupContract(), }; return setupContract; @@ -71,7 +75,7 @@ const createStartContract = (): Start => { ui: { IndexPatternSelect: jest.fn(), }, - indexPatterns: indexPatternsServiceMock.createStartContract(), + indexPatterns: {} as IndexPatternsContract, }; return startContract; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index c018efcad74e65..6ec0413968a553 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -26,10 +26,10 @@ import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; -import { IndexPatternsService } from './index_patterns'; +import { IndexPatterns } from './index_patterns'; +import { setNotifications, setFieldFormats } from './services'; export class DataPublicPlugin implements Plugin { - private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); private readonly autocomplete = new AutocompleteProviderRegister(); private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; @@ -58,6 +58,10 @@ export class DataPublicPlugin implements Plugin( 'Notifications' diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 5e1d8797ebf089..dd70c5646f708e 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -25,26 +25,25 @@ import { ISearchSetup, ISearchStart } from './search'; import { IGetSuggestions } from './suggestions_provider/types'; import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; -import { IndexPatternsStart, IndexPatternsSetup } from './index_patterns'; +import { IndexPatternsContract } from './index_patterns'; export interface DataPublicPluginSetup { autocomplete: AutocompletePublicPluginSetup; search: ISearchSetup; fieldFormats: FieldFormatsSetup; query: QuerySetup; - indexPatterns?: IndexPatternsSetup; } export interface DataPublicPluginStart { autocomplete: AutocompletePublicPluginStart; getSuggestions: IGetSuggestions; + indexPatterns: IndexPatternsContract; search: ISearchStart; fieldFormats: FieldFormatsStart; query: QueryStart; ui: { IndexPatternSelect: React.ComponentType; }; - indexPatterns: IndexPatternsStart; } export * from './autocomplete_provider/types'; diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index f06fc4dd748ccd..80a5ede5670543 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -172,13 +172,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -798,13 +792,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -1412,13 +1400,7 @@ exports[`QueryStringInput Should pass the query language to the language switche "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -2035,13 +2017,7 @@ exports[`QueryStringInput Should pass the query language to the language switche "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -2649,13 +2625,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], @@ -3272,13 +3242,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` "register": [MockFunction], }, "getSuggestions": [MockFunction], - "indexPatterns": Object { - "FieldList": Object {}, - "IndexPatternSelect": [MockFunction], - "flattenHitWrapper": [MockFunction], - "formatHitProvider": [MockFunction], - "indexPatterns": [MockFunction], - }, + "indexPatterns": Object {}, "query": Object { "filterManager": [MockFunction], "savedQueries": [MockFunction], diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index b76da4b4eca3ef..f4659ffa120d40 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -21,20 +21,26 @@ import sinon from 'sinon'; // TODO: We should not be importing from the data plugin directly here; this is only necessary // because it is one of the few places that we need to access the IndexPattern class itself, rather // than just the type. Doing this as a temporary measure; it will be left behind when migrating to NP. -import { IndexPattern } from '../../legacy/core_plugins/data/public/'; + import { FieldList, - getRoutes, - formatHitProvider, - flattenHitWrapper, -} from 'ui/index_patterns'; -import { FIELD_FORMAT_IDS, + IndexPattern, + indexPatterns, } from '../../plugins/data/public'; +import { setFieldFormats } from '../../plugins/data/public/services'; + +setFieldFormats({ + getDefaultInstance: () => ({ + getConverterFor: () => value => value, + convert: value => JSON.stringify(value) + }), +}); + import { getFieldFormatsRegistry } from './stub_field_formats'; -export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { +export default function StubIndexPattern(pattern, getConfig, timeField, fields, uiSettings) { const registeredFieldFormats = getFieldFormatsRegistry(uiSettings); this.id = pattern; @@ -49,11 +55,11 @@ export default function StubIndexPattern(pattern, getConfig, timeField, fields, this.getSourceFiltering = sinon.stub(); this.metaFields = ['_id', '_type', '_source']; this.fieldFormatMap = {}; - this.routes = getRoutes(); + this.routes = indexPatterns.getRoutes(); this.getComputedFields = IndexPattern.prototype.getComputedFields.bind(this); - this.flattenHit = flattenHitWrapper(this, this.metaFields); - this.formatHit = formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); + this.flattenHit = indexPatterns.flattenHitWrapper(this, this.metaFields); + this.formatHit = indexPatterns.formatHitProvider(this, registeredFieldFormats.getDefaultInstance(FIELD_FORMAT_IDS.STRING)); this.fieldsFetcher = { apiClient: { baseUrl: '' } }; this.formatField = this.formatHit.formatField; diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index 83c610800b89ba..8771181639f4d3 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -8,7 +8,6 @@ import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; -import { setup as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; const getAutocompleteProvider = (language: string) => npStart.plugins.data.autocomplete.getProvider(language); @@ -64,7 +63,7 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { if (this.cachedIndexPattern) { return this.cachedIndexPattern; } - const res = await data.indexPatterns.indexPatterns.getFieldsForWildcard({ + const res = await npStart.plugins.data.indexPatterns.getFieldsForWildcard({ pattern: this.indexPatternName, }); if (isEmpty(res.fields)) { diff --git a/x-pack/legacy/plugins/file_upload/public/kibana_services.js b/x-pack/legacy/plugins/file_upload/public/kibana_services.js index 3c00ab57096607..16450406291957 100644 --- a/x-pack/legacy/plugins/file_upload/public/kibana_services.js +++ b/x-pack/legacy/plugins/file_upload/public/kibana_services.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; +import { npStart } from 'ui/new_platform'; import { DEFAULT_KBN_VERSION } from '../common/constants/file_import'; -export const indexPatternService = data.indexPatterns.indexPatterns; +export const indexPatternService = npStart.plugins.data.indexPatterns; export let savedObjectsClient; export let basePath; diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index 988aa786950950..712d08c106425e 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -18,7 +18,6 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { GraphPlugin } from './plugin'; @@ -51,7 +50,6 @@ async function getAngularInjectedDependencies(): Promise; navigation: NavigationStart; } @@ -31,7 +29,6 @@ export interface GraphPluginStartDependencies { } export class GraphPlugin implements Plugin { - private dataStart: DataStart | null = null; private navigationStart: NavigationStart | null = null; private npDataStart: ReturnType | null = null; private savedObjectsClient: SavedObjectsClientContract | null = null; @@ -61,7 +58,7 @@ export class GraphPlugin implements Plugin { chrome: contextCore.chrome, config: contextCore.uiSettings, toastNotifications: contextCore.notifications.toasts, - indexPatterns: this.dataStart!.indexPatterns.indexPatterns, + indexPatterns: this.npDataStart!.indexPatterns, ...this.angularDependencies!, }); }, @@ -70,10 +67,9 @@ export class GraphPlugin implements Plugin { start( core: CoreStart, - { data, npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies + { npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies ) { this.navigationStart = navigation; - this.dataStart = data; this.npDataStart = npData; this.angularDependencies = angularDependencies; this.savedObjectsClient = core.savedObjects.client; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index 1beee2e73721bf..0f3c52d38a01cd 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -21,7 +21,6 @@ import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_to import { confirmModalFactory } from 'ui/modals/confirm_modal'; // type imports -import { DataStart } from 'src/legacy/core_plugins/data/public'; import { AppMountContext, ChromeStart, @@ -32,7 +31,10 @@ import { } from 'kibana/public'; // @ts-ignore import { initGraphApp } from './app'; -import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public'; +import { + Plugin as DataPlugin, + IndexPatternsContract, +} from '../../../../../src/plugins/data/public'; import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; /** @@ -50,7 +52,7 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies { chrome: ChromeStart; config: IUiSettingsClient; toastNotifications: ToastsStart; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; + indexPatterns: IndexPatternsContract; npData: ReturnType; savedObjectsClient: SavedObjectsClientContract; xpackInfo: { get(path: string): unknown }; diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index ce05af46ade668..3ffc7bcf275d7d 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -106,14 +106,10 @@ describe('Lens App', () => { }, }, }, - }, - dataShim: { indexPatterns: { - indexPatterns: { - get: jest.fn(id => { - return new Promise(resolve => resolve({ id })); - }), - }, + get: jest.fn(id => { + return new Promise(resolve => resolve({ id })); + }), }, }, storage: { @@ -238,7 +234,7 @@ describe('Lens App', () => { await waitForPromises(); expect(args.docStorage.load).toHaveBeenCalledWith('1234'); - expect(args.dataShim.indexPatterns.indexPatterns.get).toHaveBeenCalledWith('1'); + expect(args.data.indexPatterns.get).toHaveBeenCalledWith('1'); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: 'fake query', diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index c29b0df9ee9fa3..fda07c9b6bb774 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -11,12 +11,7 @@ import { i18n } from '@kbn/i18n'; import { Query, DataPublicPluginStart } from 'src/plugins/data/public'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { AppMountContext, NotificationsStart } from 'src/core/public'; -import { - DataStart, - IndexPattern as IndexPatternInstance, - IndexPatterns as IndexPatternsService, - SavedQuery, -} from 'src/legacy/core_plugins/data/public'; +import { SavedQuery } from 'src/legacy/core_plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; @@ -24,7 +19,11 @@ import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; import { NativeRenderer } from '../native_renderer'; import { trackUiEvent } from '../lens_ui_telemetry'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + IndexPattern as IndexPatternInstance, + IndexPatternsContract, +} from '../../../../../../src/plugins/data/public'; interface State { isLoading: boolean; @@ -46,7 +45,6 @@ interface State { export function App({ editorFrame, data, - dataShim, core, storage, docId, @@ -56,7 +54,6 @@ export function App({ editorFrame: EditorFrameInstance; data: DataPublicPluginStart; core: AppMountContext['core']; - dataShim: DataStart; storage: IStorageWrapper; docId?: string; docStorage: SavedObjectStore; @@ -119,7 +116,7 @@ export function App({ .then(doc => { getAllIndexPatterns( doc.state.datasourceMetaData.filterableIndexPatterns, - dataShim.indexPatterns.indexPatterns, + data.indexPatterns, core.notifications ) .then(indexPatterns => { @@ -286,7 +283,7 @@ export function App({ ) { getAllIndexPatterns( filterableIndexPatterns, - dataShim.indexPatterns.indexPatterns, + data.indexPatterns, core.notifications ).then(indexPatterns => { if (indexPatterns) { @@ -349,7 +346,7 @@ export function App({ export async function getAllIndexPatterns( ids: Array<{ id: string }>, - indexPatternsService: IndexPatternsService, + indexPatternsService: IndexPatternsContract, notifications: NotificationsStart ): Promise { try { diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 6493af28f89ea9..b1eac8e287bd89 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -82,7 +82,7 @@ export class AppPlugin { if (this.startDependencies === null) { throw new Error('mounted before start phase'); } - const { data, dataShim, savedObjectsClient, editorFrame } = this.startDependencies; + const { data, savedObjectsClient, editorFrame } = this.startDependencies; addHelpMenuToAppChrome(context.core.chrome); const instance = editorFrame.createInstance({}); @@ -100,7 +100,6 @@ export class AppPlugin { editorFrame.setup(npSetup.core, { - data: dataSetup, + data: npSetup.plugins.data, embeddable: npSetup.plugins.embeddable, expressions: npSetup.plugins.expressions, }); export const editorFrameStart = () => editorFrame.start(npStart.core, { - data: dataStart, + data: npStart.plugins.data, embeddable: npStart.plugins.embeddable, expressions: npStart.plugins.expressions, chrome, diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index 71690145427106..609f075e194f8c 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -6,12 +6,12 @@ import { getRequestInspectorStats, getResponseInspectorStats } from '../../../../../src/legacy/ui/public/courier'; export { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; import { esFilters } from '../../../../../src/plugins/data/public'; +import { npStart } from 'ui/new_platform'; export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; export { SearchSource } from '../../../../../src/legacy/ui/public/courier'; -export const indexPatternService = data.indexPatterns.indexPatterns; +export const indexPatternService = npStart.plugins.data.indexPatterns; export async function fetchSearchSourceAndRecordWithInspector({ searchSource, diff --git a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts index 778872d8183eea..777327c639ebcd 100644 --- a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts +++ b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_patterns.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatterns } from 'ui/index_patterns'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; export const indexPatternsMock = (new (class { fieldFormats = []; @@ -18,4 +18,4 @@ export const indexPatternsMock = (new (class { getIds = jest.fn(); getTitles = jest.fn(); make = jest.fn(); -})() as unknown) as IndexPatterns; +})() as unknown) as IndexPatternsContract; 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 aed230a53f62a1..00989245e20e7f 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 @@ -9,7 +9,10 @@ import React from 'react'; import { KibanaConfig } from 'src/legacy/server/kbn_server'; import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types'; -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; +import { + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../../src/plugins/data/public'; // set() method is missing in original d.ts export interface KibanaConfigTypeFix extends KibanaConfig { @@ -20,7 +23,7 @@ export interface KibanaContextValue { combinedQuery: any; currentIndexPattern: IndexPattern; currentSavedSearch: SavedSearch; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; kibanaConfig: KibanaConfigTypeFix; } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx index c41285f40d64b2..1d4ac85ae2e872 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/directive.tsx @@ -11,8 +11,8 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; import { InjectorService } from '../../../../../common/types/angular'; import { createSearchItems } from '../../../jobs/new_job/utils/new_job_utils'; @@ -29,7 +29,7 @@ module.directive('mlDataFrameAnalyticsExploration', ($injector: InjectorService) const globalState = $injector.get('globalState'); globalState.fetch(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx index 8299ff53393bb4..5d97ed6dfcd3d4 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/directive.tsx @@ -11,10 +11,10 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { InjectorService } from '../../../../../common/types/angular'; import { createSearchItems } from '../../../jobs/new_job/utils/new_job_utils'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; import { KibanaConfigTypeFix, KibanaContext } from '../../../contexts/kibana'; @@ -25,7 +25,7 @@ module.directive('mlDataFrameAnalyticsManagement', ($injector: InjectorService) scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index 3776245d90c819..99e61d5937c1d8 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -5,8 +5,8 @@ */ import React, { FC, Fragment } from 'react'; -import { IndexPatterns } from 'ui/index_patterns'; import { timefilter } from 'ui/timefilter'; +import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; import { KibanaConfigTypeFix } from '../../contexts/kibana'; import { NavigationMenu } from '../../components/navigation_menu'; @@ -15,7 +15,7 @@ import { NavigationMenu } from '../../components/navigation_menu'; import { FileDataVisualizerView } from './components/file_datavisualizer_view/index'; export interface FileDataVisualizerPageProps { - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; kibanaConfig: KibanaConfigTypeFix; } diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx index 291e03a96e85f8..7ca2db041da295 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer_directive.tsx @@ -14,7 +14,6 @@ import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import uiRoutes from 'ui/routes'; -import { IndexPatterns } from 'ui/index_patterns'; import { KibanaConfigTypeFix } from '../../contexts/kibana'; import { getFileDataVisualizerBreadcrumbs } from './breadcrumbs'; import { InjectorService } from '../../../../common/types/angular'; @@ -24,6 +23,7 @@ import { getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes'; import { loadMlServerInfo } from '../../services/ml_server_info'; import { loadIndexPatterns } from '../../util/index_utils'; import { FileDataVisualizerPage, FileDataVisualizerPageProps } from './file_datavisualizer'; +import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; const template = `
@@ -47,7 +47,7 @@ module.directive('fileDatavisualizerPage', function($injector: InjectorService) scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const props: FileDataVisualizerPageProps = { diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx index 58cd1c2c6fd0c0..5de7cb6b71acb1 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/directive.tsx @@ -12,7 +12,7 @@ import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { I18nContext } from 'ui/i18n'; -import { IndexPatterns } from 'ui/index_patterns'; +import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; import { InjectorService } from '../../../../common/types/angular'; import { KibanaConfigTypeFix, KibanaContext } from '../../contexts/kibana/kibana_context'; @@ -25,7 +25,7 @@ module.directive('mlDataVisualizer', ($injector: InjectorService) => { scope: {}, restrict: 'E', link: (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx index 8d54ca65a2852a..3f2a7e553c7e0a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/directive.tsx @@ -11,7 +11,6 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { timefilter } from 'ui/timefilter'; -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { InjectorService } from '../../../../../../common/types/angular'; @@ -19,6 +18,7 @@ import { createSearchItems } from '../../utils/new_job_utils'; import { Page } from './page'; import { KibanaContext, KibanaConfigTypeFix } from '../../../../contexts/kibana'; +import { IndexPatternsContract } from '../../../../../../../../../../src/plugins/data/public'; module.directive('mlJobTypePage', ($injector: InjectorService) => { return { @@ -29,7 +29,7 @@ module.directive('mlJobTypePage', ($injector: InjectorService) => { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx index db4078ba1bbc86..d152dfc488ff86 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/directive.tsx @@ -11,9 +11,9 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { timefilter } from 'ui/timefilter'; -import { IndexPatterns } from 'ui/index_patterns'; - import { I18nContext } from 'ui/i18n'; +import { IndexPatternsContract } from '../../../../../../../../../../src/plugins/data/public'; + import { InjectorService } from '../../../../../../common/types/angular'; import { createSearchItems } from '../../utils/new_job_utils'; import { Page, PageProps } from './page'; @@ -29,7 +29,7 @@ module.directive('mlNewJobPage', ($injector: InjectorService) => { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); const existingJobsAndGroups = $route.current.locals.existingJobsAndGroups; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx index 2d08a1da074591..4ed12dfff4c207 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/directive.tsx @@ -11,7 +11,6 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); import { timefilter } from 'ui/timefilter'; -import { IndexPatterns } from 'ui/index_patterns'; import { I18nContext } from 'ui/i18n'; import { InjectorService } from '../../../../../common/types/angular'; @@ -20,6 +19,7 @@ import { createSearchItems } from '../utils/new_job_utils'; import { Page } from './page'; import { KibanaContext, KibanaConfigTypeFix } from '../../../contexts/kibana'; +import { IndexPatternsContract } from '../../../../../../../../../src/plugins/data/public'; module.directive('mlRecognizePage', ($injector: InjectorService) => { return { @@ -30,7 +30,7 @@ module.directive('mlRecognizePage', ($injector: InjectorService) => { timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); - const indexPatterns = $injector.get('indexPatterns'); + const indexPatterns = $injector.get('indexPatterns'); const kibanaConfig = $injector.get('config'); const $route = $injector.get('$route'); diff --git a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts index aeec71462308e2..f79515c80556a3 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { SavedSearchLoader } from 'src/legacy/core_plugins/kibana/public/discover/types'; import { @@ -15,13 +14,17 @@ import { NewJobCaps, EVENT_RATE_FIELD_ID, } from '../../../common/types/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { + ES_FIELD_TYPES, + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../src/plugins/data/public'; import { ml } from './ml_api_service'; // called in the angular routing resolve block to initialize the // newJobCapsService with the currently selected index pattern export function loadNewJobCapabilities( - indexPatterns: IndexPatterns, + indexPatterns: IndexPatternsContract, savedSearches: SavedSearchLoader, $route: Record ) { diff --git a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts index f25821e8ca1ca3..99882b0243be8f 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts @@ -6,19 +6,19 @@ import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { SavedObjectAttributes, SimpleSavedObject } from 'kibana/public'; import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; import { SavedSearchLoader } from '../../../../../../../src/legacy/core_plugins/kibana/public/discover/types'; -import { start as data } from '../../../../../../../src/legacy/core_plugins/data/public/legacy'; +import { IndexPattern, IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; type IndexPatternSavedObject = SimpleSavedObject; let indexPatternCache: IndexPatternSavedObject[] = []; -let fullIndexPatterns: IndexPatterns | null = null; +let fullIndexPatterns: IndexPatternsContract | null = null; export function loadIndexPatterns() { - fullIndexPatterns = data.indexPatterns.indexPatterns; + fullIndexPatterns = npStart.plugins.data.indexPatterns; const savedObjectsClient = chrome.getSavedObjectsClient(); return savedObjectsClient .find({ @@ -49,7 +49,10 @@ export function getIndexPatternIdFromName(name: string) { return null; } -export function loadCurrentIndexPattern(indexPatterns: IndexPatterns, $route: Record) { +export function loadCurrentIndexPattern( + indexPatterns: IndexPatternsContract, + $route: Record +) { fullIndexPatterns = indexPatterns; return fullIndexPatterns.get($route.current.params.index); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 24e304b0010d06..1d93b9251bbd69 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -9,11 +9,11 @@ import routes from 'ui/routes'; import { capabilities } from 'ui/capabilities'; import { kfetch } from 'ui/kfetch'; import { fatalError, toastNotifications } from 'ui/notify'; +import { npStart } from 'ui/new_platform'; import template from 'plugins/security/views/management/edit_role/edit_role.html'; import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import 'plugins/security/services/shield_indices'; -import { start as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import { SpacesManager } from '../../../../../spaces/public/lib'; import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; @@ -75,8 +75,7 @@ const routeDefinition = (action) => ({ .then(users => _.map(users, 'username')); }, indexPatterns() { - const { indexPatterns } = data.indexPatterns; - return indexPatterns.getTitles(); + return npStart.plugins.data.indexPatterns.getTitles(); }, spaces(spacesEnabled) { if (spacesEnabled) { diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts index b40645799fb4b0..62c87413c9bced 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts @@ -5,11 +5,8 @@ */ import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/public'; -import { - IndexPattern as IndexPatternType, - IndexPatterns as IndexPatternsType, -} from 'ui/index_patterns'; -import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { IndexPattern as IndexPatternType } from 'ui/index_patterns'; +import { esQuery, IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; type IndexPatternId = string; type SavedSearchId = string; @@ -23,7 +20,7 @@ export let refreshIndexPatterns: () => Promise; export function loadIndexPatterns( savedObjectsClient: SavedObjectsClientContract, - indexPatterns: IndexPatternsType + indexPatterns: IndexPatternsContract ) { fullIndexPatterns = indexPatterns; return savedObjectsClient @@ -56,7 +53,7 @@ export function loadIndexPatterns( type CombinedQuery = Record<'bool', any> | unknown; export function loadCurrentIndexPattern( - indexPatterns: IndexPatternsType, + indexPatterns: IndexPatternsContract, indexPatternId: IndexPatternId ) { fullIndexPatterns = indexPatterns; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx index e3515991e7bb1f..3a3bd33ff6d704 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx @@ -6,12 +6,11 @@ import React, { createContext, useContext, FC } from 'react'; -import { - IndexPattern as IndexPatternType, - IndexPatterns as IndexPatternsType, -} from 'ui/index_patterns'; - import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kibana/public/discover/types'; +import { + IndexPattern, + IndexPatternsContract, +} from '../../../../../../../../src/plugins/data/public'; import { KibanaConfig } from '../../../../../../../../src/legacy/server/kbn_server'; // set() method is missing in original d.ts @@ -25,9 +24,9 @@ interface UninitializedKibanaContextValue { export interface InitializedKibanaContextValue { combinedQuery: any; - currentIndexPattern: IndexPatternType; + currentIndexPattern: IndexPattern; currentSavedSearch: SavedSearch; - indexPatterns: IndexPatternsType; + indexPatterns: IndexPatternsContract; initialized: boolean; kbnBaseUrl: string; kibanaConfig: KibanaConfigTypeFix; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx index 7d615bfa521f0a..0a9de49168ad47 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx @@ -8,8 +8,6 @@ import React, { useEffect, useState, FC } from 'react'; import { npStart } from 'ui/new_platform'; -import { start as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; - import { useAppDependencies } from '../../app_dependencies'; import { @@ -21,7 +19,7 @@ import { import { KibanaContext, KibanaContextValue } from './kibana_context'; -const { indexPatterns } = data.indexPatterns; +const indexPatterns = npStart.plugins.data.indexPatterns; const savedObjectsClient = npStart.core.savedObjects.client; interface Props { From e9299092f03bc7ea8e19ede3bb87f70158dbde68 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Mon, 9 Dec 2019 12:37:54 +0100 Subject: [PATCH 02/36] Expose SavedObjectsStart from AppMountContext (#52059) --- .../core/public/kibana-plugin-public.appmountcontext.core.md | 1 + .../core/public/kibana-plugin-public.appmountcontext.md | 2 +- src/core/public/application/types.ts | 3 +++ src/core/public/core_system.ts | 1 + src/core/public/public.api.md | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md index 960d610b589b8d..e01a5e9da50d54 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.core.md @@ -17,6 +17,7 @@ core: { i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; + savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; diff --git a/docs/development/core/public/kibana-plugin-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-public.appmountcontext.md index e12121e0e3ebbc..68a1c27b118366 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountcontext.md +++ b/docs/development/core/public/kibana-plugin-public.appmountcontext.md @@ -16,5 +16,5 @@ export interface AppMountContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
uiSettings: IUiSettingsClient;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | +| [core](./kibana-plugin-public.appmountcontext.core.md) | {
application: Pick<ApplicationStart, 'capabilities' | 'navigateToApp'>;
chrome: ChromeStart;
docLinks: DocLinksStart;
http: HttpStart;
i18n: I18nStart;
notifications: NotificationsStart;
overlays: OverlayStart;
savedObjects: SavedObjectsStart;
uiSettings: IUiSettingsClient;
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
} | Core service APIs available to mounted applications. | diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 6313a27b6b821f..a031ab60704134 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -30,6 +30,7 @@ import { OverlayStart } from '../overlays'; import { PluginOpaqueId } from '../plugins'; import { IUiSettingsClient } from '../ui_settings'; import { RecursiveReadonly } from '../../utils'; +import { SavedObjectsStart } from '../saved_objects'; /** @public */ export interface AppBase { @@ -118,6 +119,8 @@ export interface AppMountContext { notifications: NotificationsStart; /** {@link OverlayStart} */ overlays: OverlayStart; + /** {@link SavedObjectsStart} */ + savedObjects: SavedObjectsStart; /** {@link IUiSettingsClient} */ uiSettings: IUiSettingsClient; /** diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 2404459ad13834..4818484b00819c 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -255,6 +255,7 @@ export class CoreSystem { i18n, notifications, overlays, + savedObjects, uiSettings, injectedMetadata: pick(injectedMetadata, ['getInjectedVar']), })); diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 2dbf4d54efef6f..6fbc7324ce3936 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -63,6 +63,7 @@ export interface AppMountContext { i18n: I18nStart; notifications: NotificationsStart; overlays: OverlayStart; + savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; From cffb4dcea84f50e05a5e3f758aede5edb49971f5 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 9 Dec 2019 12:48:08 +0100 Subject: [PATCH 03/36] [ML] Fix table factory usage. (#52486) Moves calling the table factory out of components to avoid instantiation on every render call. Note all of this is a workaround for our own typescript version of EuiInMemoryTable. Once the original table has been migrated to TS, we should be able to remove the custom code. --- .../application/overview/components/analytics_panel/table.tsx | 4 ++-- .../overview/components/anomaly_detection_panel/table.tsx | 4 ++-- .../components/transform_list/expanded_row_preview_pane.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx index 156e53b19874f0..ff81f0e87aca88 100644 --- a/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/application/overview/components/analytics_panel/table.tsx @@ -26,6 +26,8 @@ import { import { AnalyticsViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; +const MlInMemoryTable = mlInMemoryTableFactory(); + interface Props { items: DataFrameAnalyticsListRow[]; } @@ -113,8 +115,6 @@ export const AnalyticsTable: FC = ({ items }) => { }, }; - const MlInMemoryTable = mlInMemoryTableFactory(); - return ( (); + // Used to pass on attribute names to table columns export enum AnomalyDetectionListColumns { id = 'id', @@ -195,8 +197,6 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }, }; - const MlInMemoryTable = mlInMemoryTableFactory(); - return ( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx index 5a5e8308b8d571..6ee03ccca16e12 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -22,6 +22,8 @@ import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/p import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils'; import { transformTableFactory } from './transform_table'; +const TransformTable = transformTableFactory(); + interface Props { transformConfig: TransformPivotConfig; } @@ -197,8 +199,6 @@ export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => { const transformTableLoading = previewData.length === 0 && isLoading === true; const dataTestSubj = `transformPreviewTabContent${!transformTableLoading ? ' loaded' : ''}`; - const TransformTable = transformTableFactory(); - return (
Date: Mon, 9 Dec 2019 14:29:48 +0100 Subject: [PATCH 04/36] fix flaky test (#52395) --- .../public/components/search_bar.test.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index 8d5e27f1e9aec9..9dec725a7a2a13 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -51,6 +51,9 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { query: { savedQueries: {}, }, + autocomplete: { + getProvider: () => undefined, + }, }, }; @@ -63,8 +66,7 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { ); } -// FLAKY: https://github.com/elastic/kibana/issues/52246 -describe.skip('search_bar', () => { +describe('search_bar', () => { const defaultProps = { isLoading: false, onQuerySubmit: jest.fn(), @@ -89,21 +91,23 @@ describe.skip('search_bar', () => { ); }); - function mountSearchBar() { + async function mountSearchBar() { jest.clearAllMocks(); const wrappedSearchBar = wrapSearchBarInContext({ ...defaultProps }); - instance = mountWithIntl({wrappedSearchBar}); + await act(async () => { + instance = mountWithIntl({wrappedSearchBar}); + }); } - it('should render search bar and fetch index pattern', () => { - mountSearchBar(); + it('should render search bar and fetch index pattern', async () => { + await mountSearchBar(); expect(defaultProps.indexPatternProvider.get).toHaveBeenCalledWith('123'); }); it('should render search bar and submit queries', async () => { - mountSearchBar(); + await mountSearchBar(); await waitForIndexPatternFetch(); @@ -119,7 +123,7 @@ describe.skip('search_bar', () => { }); it('should translate kql query into JSON dsl', async () => { - mountSearchBar(); + await mountSearchBar(); await waitForIndexPatternFetch(); @@ -137,8 +141,8 @@ describe.skip('search_bar', () => { }); }); - it('should open index pattern picker', () => { - mountSearchBar(); + it('should open index pattern picker', async () => { + await mountSearchBar(); // pick the button component out of the tree because // it's part of a popover and thus not covered by enzyme From 3d36356cab449b693104cd9e59a21b88353dbd11 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 9 Dec 2019 14:54:23 +0100 Subject: [PATCH 05/36] License Management to New Platform (#51886) * License Management public -> NP * - Server to NP - Slight update to filepicker style (center it) * Fix snapshots and types * Server-side: separate new and legacy dependencies [skip ci] * Fix license upload route after refactor * Client side: separate new from legacy dependencies * xpackInfo -> xPackInfo * Fix types [skip ci] * Remove kbnUrl, autoLogout. Add history and update paths. * Update upload license test * Remove use of legacy chrome, remove use of k7breadcrumbs, replace some common strings with variable and use NP i18n --- ...t.js.snap => upload_license.test.tsx.snap} | 909 ++++++++++-------- .../__jest__/add_license.test.js | 2 +- .../__jest__/license_status.test.js | 2 +- .../__jest__/request_trial_extension.test.js | 2 +- .../__jest__/revert_to_basic.test.js | 2 +- .../__jest__/start_trial.test.js | 2 +- .../__jest__/telemetry_opt_in.test.js | 4 +- ...icense.test.js => upload_license.test.tsx} | 82 +- .../license_management/__jest__/util/util.js | 8 +- .../constants/{base_path.js => base_path.ts} | 0 .../{external_links.js => external_links.ts} | 2 +- .../common/constants/{index.js => index.ts} | 0 .../{permissions.js => permissions.ts} | 0 .../common/constants/plugin.ts | 13 + .../plugins/license_management/index.js | 38 - .../plugins/license_management/index.ts | 32 + .../public/{index.js => legacy.ts} | 0 .../license_management/public/lib/es.js | 71 -- .../license_management/public/main.html | 3 - ...ement_section.js => management_section.ts} | 10 +- .../application}/_license_management.scss | 0 .../application}/app.container.js | 2 +- .../public/{ => np_ready/application}/app.js | 10 +- .../public/np_ready/application/boot.tsx | 75 ++ .../application/breadcrumbs.ts} | 29 +- .../components/telemetry_opt_in/index.js | 0 .../telemetry_opt_in/telemetry_opt_in.js | 0 .../{ => np_ready/application}/index.scss | 2 +- .../np_ready/application/index.ts} | 4 +- .../np_ready/application/lib/docs_links.ts} | 12 +- .../public/np_ready/application/lib/es.ts | 62 ++ .../application}/lib/telemetry.js | 8 +- .../application}/sections/index.js | 0 .../add_license/add_license.js | 2 +- .../license_dashboard/add_license/index.js | 0 .../sections/license_dashboard/index.js | 2 +- .../license_dashboard.container.js | 14 + .../license_dashboard/license_dashboard.js | 3 +- .../license_dashboard/license_status/index.js | 0 .../license_status.container.js | 2 +- .../license_status/license_status.js | 0 .../request_trial_extension/index.js | 0 .../request_trial_extension.container.js | 2 +- .../request_trial_extension.js | 2 +- .../revert_to_basic/index.js | 0 .../revert_to_basic.container.js | 2 +- .../revert_to_basic/revert_to_basic.js | 2 +- .../license_dashboard/start_trial/index.js | 0 .../start_trial/start_trial.container.js | 2 +- .../start_trial/start_trial.js | 8 +- .../sections/upload_license/index.js | 0 .../upload_license.container.js | 4 +- .../sections/upload_license/upload_license.js | 27 +- .../store/actions/add_error_message.js | 0 .../application}/store/actions/add_license.js | 0 .../application}/store/actions/index.js | 0 .../application}/store/actions/permissions.js | 4 +- .../store/actions/set_breadcrumb.ts | 22 + .../application}/store/actions/start_basic.js | 11 +- .../application}/store/actions/start_trial.js | 13 +- .../store/actions/upload_license.js | 23 +- .../{ => np_ready/application}/store/index.js | 0 .../application}/store/reducers/index.js | 2 +- .../application}/store/reducers/license.js | 0 .../store/reducers/license_management.js} | 0 .../store/reducers/permissions.js | 0 .../reducers/start_basic_license_status.js | 0 .../store/reducers/trial_status.js | 0 .../store/reducers/upload_error_message.js | 0 .../store/reducers/upload_status.js | 0 .../{ => np_ready/application}/store/store.js | 2 +- .../public/np_ready/index.ts | 9 + .../public/np_ready/plugin.ts | 50 + .../public/register_route.js | 109 --- .../public/register_route.ts | 98 ++ .../license_management/server/lib/license.js | 27 - .../server/lib/start_basic.js | 28 - .../server/lib/start_trial.js | 38 - .../index.ts} | 9 +- .../server/np_ready/lib/license.ts | 33 + .../lib/permissions.ts} | 22 +- .../server/np_ready/lib/start_basic.ts | 34 + .../server/np_ready/lib/start_trial.ts | 47 + .../server/np_ready/plugin.ts | 35 + .../routes/api/license/index.ts} | 0 .../api/license/register_license_route.ts | 32 + .../api/license/register_permissions_route.ts | 29 + .../api/license/register_start_basic_route.ts | 27 + .../license/register_start_trial_routes.ts | 34 + .../server/np_ready/types.ts | 24 + .../api/license/register_permissions_route.js | 13 - .../license/register_start_trial_routes.js | 16 - .../xpack_main/public/components/index.js | 7 +- 93 files changed, 1349 insertions(+), 907 deletions(-) rename x-pack/legacy/plugins/license_management/__jest__/__snapshots__/{upload_license.test.js.snap => upload_license.test.tsx.snap} (77%) rename x-pack/legacy/plugins/license_management/__jest__/{upload_license.test.js => upload_license.test.tsx} (62%) rename x-pack/legacy/plugins/license_management/common/constants/{base_path.js => base_path.ts} (100%) rename x-pack/legacy/plugins/license_management/common/constants/{external_links.js => external_links.ts} (88%) rename x-pack/legacy/plugins/license_management/common/constants/{index.js => index.ts} (100%) rename x-pack/legacy/plugins/license_management/common/constants/{permissions.js => permissions.ts} (100%) create mode 100644 x-pack/legacy/plugins/license_management/common/constants/plugin.ts delete mode 100644 x-pack/legacy/plugins/license_management/index.js create mode 100644 x-pack/legacy/plugins/license_management/index.ts rename x-pack/legacy/plugins/license_management/public/{index.js => legacy.ts} (100%) delete mode 100644 x-pack/legacy/plugins/license_management/public/lib/es.js delete mode 100644 x-pack/legacy/plugins/license_management/public/main.html rename x-pack/legacy/plugins/license_management/public/{management_section.js => management_section.ts} (62%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/_license_management.scss (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/app.container.js (94%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/app.js (90%) create mode 100644 x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx rename x-pack/legacy/plugins/license_management/public/{breadcrumbs.js => np_ready/application/breadcrumbs.ts} (52%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/components/telemetry_opt_in/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/components/telemetry_opt_in/telemetry_opt_in.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/index.scss (90%) rename x-pack/legacy/plugins/license_management/{common/constants/plugin.js => public/np_ready/application/index.ts} (81%) rename x-pack/legacy/plugins/license_management/{server/routes/api/license/register_license_route.js => public/np_ready/application/lib/docs_links.ts} (55%) create mode 100644 x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.ts rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/lib/telemetry.js (68%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/add_license/add_license.js (94%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/add_license/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/index.js (78%) create mode 100644 x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/license_dashboard.js (88%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/license_status/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/license_status/license_status.container.js (95%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/license_status/license_status.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/request_trial_extension/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js (92%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/request_trial_extension/request_trial_extension.js (96%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/revert_to_basic/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js (95%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/revert_to_basic/revert_to_basic.js (98%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/start_trial/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/start_trial/start_trial.container.js (97%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/license_dashboard/start_trial/start_trial.js (96%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/upload_license/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/upload_license/upload_license.container.js (89%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/sections/upload_license/upload_license.js (89%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/add_error_message.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/add_license.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/permissions.js (86%) create mode 100644 x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/start_basic.js (88%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/start_trial.js (74%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/actions/upload_license.js (90%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/index.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/index.js (80%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/license.js (100%) rename x-pack/legacy/plugins/license_management/public/{store/reducers/licenseManagement.js => np_ready/application/store/reducers/license_management.js} (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/permissions.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/start_basic_license_status.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/trial_status.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/upload_error_message.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/reducers/upload_status.js (100%) rename x-pack/legacy/plugins/license_management/public/{ => np_ready/application}/store/store.js (93%) create mode 100644 x-pack/legacy/plugins/license_management/public/np_ready/index.ts create mode 100644 x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts delete mode 100644 x-pack/legacy/plugins/license_management/public/register_route.js create mode 100644 x-pack/legacy/plugins/license_management/public/register_route.ts delete mode 100644 x-pack/legacy/plugins/license_management/server/lib/license.js delete mode 100644 x-pack/legacy/plugins/license_management/server/lib/start_basic.js delete mode 100644 x-pack/legacy/plugins/license_management/server/lib/start_trial.js rename x-pack/legacy/plugins/license_management/server/{routes/api/license/register_start_basic_route.js => np_ready/index.ts} (53%) create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/lib/license.ts rename x-pack/legacy/plugins/license_management/server/{lib/permissions.js => np_ready/lib/permissions.ts} (59%) create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.ts create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.ts create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/plugin.ts rename x-pack/legacy/plugins/license_management/server/{routes/api/license/index.js => np_ready/routes/api/license/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.ts create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.ts create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.ts create mode 100644 x-pack/legacy/plugins/license_management/server/np_ready/types.ts delete mode 100644 x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js delete mode 100644 x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap similarity index 77% rename from x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap rename to x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 9d5e91c2d5f16b..5d6f10b1bda3ee 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -130,6 +130,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem ] } needsAcknowledgement={true} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -844,99 +845,115 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
- +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+
- + @@ -1164,6 +1181,7 @@ exports[`UploadLicense should display an error when ES says license is expired 1 errorMessage="The supplied license has expired." isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -1291,103 +1309,119 @@ exports[`UploadLicense should display an error when ES says license is expired 1
- +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+
- + @@ -1615,6 +1649,7 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 errorMessage="The supplied license is not valid for this product." isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -1742,103 +1777,119 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ @@ -2066,6 +2117,7 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] errorMessage="Error encountered uploading license: Check your license file." isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -2193,99 +2245,115 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ @@ -2513,6 +2581,7 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` errorMessage="Error encountered uploading license: Can not upgrade to a production license unless TLS is configured or security is disabled" isInvalid={false} needsAcknowledgement={false} + setBreadcrumb={[Function]} uploadLicense={[Function]} uploadLicenseStatus={[Function]} > @@ -2640,103 +2709,119 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` - +
- - } - onChange={[Function]} + - -
+
- - - -
- -
+ + } + onChange={[Function]} + > + +
- Select or drag your license file - -
-
+
+ + + +
+ +
+ + Select or drag your license file + +
+
+
+
+ +
-
-
-
+ +
+ -
+ diff --git a/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js b/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js index a0f4ebdd39b0eb..1d1b98fb18807e 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/add_license.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AddLicense } from '../public/sections/license_dashboard/add_license'; +import { AddLicense } from '../public/np_ready/application/sections/license_dashboard/add_license'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js b/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js index 9a6aaf95afa6bc..ff9b2e1c786b54 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/license_status.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LicenseStatus } from '../public/sections/license_dashboard/license_status'; +import { LicenseStatus } from '../public/np_ready/application/sections/license_dashboard/license_status'; import { createMockLicense, getComponent } from './util'; describe('LicenseStatus component', () => { diff --git a/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js b/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js index e990bd48317d1b..226b3bac007b0e 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/request_trial_extension.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestTrialExtension } from '../public/sections/license_dashboard/request_trial_extension'; +import { RequestTrialExtension } from '../public/np_ready/application/sections/license_dashboard/request_trial_extension'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js b/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js index 76057ff95d41fa..3b9ca4487dfb35 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/revert_to_basic.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RevertToBasic } from '../public/sections/license_dashboard/revert_to_basic'; +import { RevertToBasic } from '../public/np_ready/application/sections/license_dashboard/revert_to_basic'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js b/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js index 928c7df70d0b7c..903be98f225f2f 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/start_trial.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { StartTrial } from '../public/sections/license_dashboard/start_trial'; +import { StartTrial } from '../public/np_ready/application/sections/license_dashboard/start_trial'; import { createMockLicense, getComponent } from './util'; jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); diff --git a/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js b/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js index a92ca384e8a371..70edc67b449bb7 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/telemetry_opt_in.test.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { setTelemetryEnabled, setTelemetryOptInService } from '../public/lib/telemetry'; -import { TelemetryOptIn } from '../public/components/telemetry_opt_in'; +import { setTelemetryEnabled, setTelemetryOptInService } from '../public/np_ready/application/lib/telemetry'; +import { TelemetryOptIn } from '../public/np_ready/application/components/telemetry_opt_in'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; jest.mock('ui/capabilities', () => ({ diff --git a/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.js b/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.tsx similarity index 62% rename from x-pack/legacy/plugins/license_management/__jest__/upload_license.test.js rename to x-pack/legacy/plugins/license_management/__jest__/upload_license.test.tsx index 04da56b7ee82b3..b852daf78ca158 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.js +++ b/x-pack/legacy/plugins/license_management/__jest__/upload_license.test.tsx @@ -4,43 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ +import { httpServiceMock, chromeServiceMock } from '../../../../../src/core/public/mocks'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; import React from 'react'; import { Provider } from 'react-redux'; -import { uploadLicense } from '../public/store/actions/upload_license'; -import { licenseManagementStore } from '../public/store/store'; -import { UploadLicense } from '../public/sections/upload_license'; -import { BASE_PATH } from '../common/constants'; + +// @ts-ignore +import { uploadLicense } from '../public/np_ready/application/store/actions/upload_license'; + +// @ts-ignore +import { licenseManagementStore } from '../public/np_ready/application/store/store'; + +// @ts-ignore +import { UploadLicense } from '../public/np_ready/application/sections/upload_license'; + import { UPLOAD_LICENSE_EXPIRED, UPLOAD_LICENSE_REQUIRES_ACK, UPLOAD_LICENSE_SUCCESS, UPLOAD_LICENSE_TLS_NOT_ENABLED, UPLOAD_LICENSE_INVALID, + // @ts-ignore } from './api_responses'; -import sinon from 'sinon'; window.location.reload = () => {}; -let server = null; -let store = null; -let component = null; + +let store: any = null; +let component: any = null; const services = { - kbnUrl: { - change: jest.fn() + legacy: { + xPackInfo: { + refresh: jest.fn(), + get: () => { + return { license: { type: 'basic' } }; + }, + }, + refreshXpack: jest.fn(), + }, + http: httpServiceMock.createSetupContract(), + chrome: chromeServiceMock.createStartContract(), + history: { + replace: jest.fn(), }, - autoLogout: () => {}, - xPackInfo: { - refresh: jest.fn(), - get: () => { - return { license: { type: 'basic' } }; - } - } }; describe('UploadLicense', () => { beforeEach(() => { - server = sinon.fakeServer.create(); - server.respondImmediately = true; store = licenseManagementStore({}, services); component = ( @@ -48,57 +57,60 @@ describe('UploadLicense', () => { ); }); + afterEach(() => { - server.restore(); - services.xPackInfo.refresh.mockReset(); - services.kbnUrl.change.mockReset(); + services.legacy.xPackInfo.refresh.mockReset(); + services.history.replace.mockReset(); + jest.clearAllMocks(); }); + it('should display an error when submitting invalid JSON', async () => { const rendered = mountWithIntl(component); store.dispatch(uploadLicense('INVALID', 'trial')); rendered.update(); expect(rendered).toMatchSnapshot(); }); + it('should display an error when ES says license is invalid', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_INVALID[2])); const rendered = mountWithIntl(component); const invalidLicense = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_INVALID); await uploadLicense(invalidLicense)(store.dispatch, null, services); rendered.update(); expect(rendered).toMatchSnapshot(); }); + it('should display an error when ES says license is expired', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_EXPIRED[2])); const rendered = mountWithIntl(component); const invalidLicense = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_EXPIRED); await uploadLicense(invalidLicense)(store.dispatch, null, services); rendered.update(); expect(rendered).toMatchSnapshot(); }); + it('should display a modal when license requires acknowledgement', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_REQUIRES_ACK[2])); const unacknowledgedLicense = JSON.stringify({ - license: { type: 'basic' } + license: { type: 'basic' }, }); - server.respond(UPLOAD_LICENSE_REQUIRES_ACK); - await uploadLicense(unacknowledgedLicense, 'trial')( - store.dispatch, - null, - services - ); + await uploadLicense(unacknowledgedLicense, 'trial')(store.dispatch, null, services); const rendered = mountWithIntl(component); expect(rendered).toMatchSnapshot(); }); + it('should refresh xpack info and navigate to BASE_PATH when ES accepts new license', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_SUCCESS[2])); const validLicense = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_SUCCESS); await uploadLicense(validLicense)(store.dispatch, null, services); - expect(services.xPackInfo.refresh).toHaveBeenCalled(); - expect(services.kbnUrl.change).toHaveBeenCalledWith(BASE_PATH); + expect(services.legacy.refreshXpack).toHaveBeenCalled(); + expect(services.history.replace).toHaveBeenCalled(); }); + it('should display error when ES returns error', async () => { + services.http.put.mockResolvedValue(JSON.parse(UPLOAD_LICENSE_TLS_NOT_ENABLED[2])); const rendered = mountWithIntl(component); const license = JSON.stringify({ license: { type: 'basic' } }); - server.respond(UPLOAD_LICENSE_TLS_NOT_ENABLED); await uploadLicense(license)(store.dispatch, null, services); rendered.update(); expect(rendered).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/license_management/__jest__/util/util.js b/x-pack/legacy/plugins/license_management/__jest__/util/util.js index 537c8d28560793..57d4bf8b3ebd6f 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/util/util.js +++ b/x-pack/legacy/plugins/license_management/__jest__/util/util.js @@ -5,9 +5,10 @@ */ import { Provider } from 'react-redux'; -import { licenseManagementStore } from '../../public/store/store'; +import { licenseManagementStore } from '../../public/np_ready/application/store/store'; import React from 'react'; import { mountWithIntl } from '../../../../../test_utils/enzyme_helpers'; +import { httpServiceMock } from '../../../../../../src/core/public/mocks'; const highExpirationMillis = new Date('October 13, 2099 00:00:00Z').getTime(); @@ -22,7 +23,10 @@ export const createMockLicense = ( }; }; export const getComponent = (initialState, Component) => { - const store = licenseManagementStore(initialState); + const services = { + http: httpServiceMock.createSetupContract() + }; + const store = licenseManagementStore(initialState, services); return mountWithIntl( diff --git a/x-pack/legacy/plugins/license_management/common/constants/base_path.js b/x-pack/legacy/plugins/license_management/common/constants/base_path.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/common/constants/base_path.js rename to x-pack/legacy/plugins/license_management/common/constants/base_path.ts diff --git a/x-pack/legacy/plugins/license_management/common/constants/external_links.js b/x-pack/legacy/plugins/license_management/common/constants/external_links.ts similarity index 88% rename from x-pack/legacy/plugins/license_management/common/constants/external_links.js rename to x-pack/legacy/plugins/license_management/common/constants/external_links.ts index 66442aaee8d4a9..1ba88b2718ae6a 100644 --- a/x-pack/legacy/plugins/license_management/common/constants/external_links.js +++ b/x-pack/legacy/plugins/license_management/common/constants/external_links.ts @@ -9,5 +9,5 @@ const ELASTIC_BASE_URL = 'https://www.elastic.co/'; export const EXTERNAL_LINKS = { SUBSCRIPTIONS: `${ELASTIC_BASE_URL}subscriptions`, TRIAL_EXTENSION: `${ELASTIC_BASE_URL}trialextension`, - TRIAL_LICENSE: `${ELASTIC_BASE_URL}legal/trial_license` + TRIAL_LICENSE: `${ELASTIC_BASE_URL}legal/trial_license`, }; diff --git a/x-pack/legacy/plugins/license_management/common/constants/index.js b/x-pack/legacy/plugins/license_management/common/constants/index.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/common/constants/index.js rename to x-pack/legacy/plugins/license_management/common/constants/index.ts diff --git a/x-pack/legacy/plugins/license_management/common/constants/permissions.js b/x-pack/legacy/plugins/license_management/common/constants/permissions.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/common/constants/permissions.js rename to x-pack/legacy/plugins/license_management/common/constants/permissions.ts diff --git a/x-pack/legacy/plugins/license_management/common/constants/plugin.ts b/x-pack/legacy/plugins/license_management/common/constants/plugin.ts new file mode 100644 index 00000000000000..14b591e3834eff --- /dev/null +++ b/x-pack/legacy/plugins/license_management/common/constants/plugin.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 { i18n } from '@kbn/i18n'; + +export const PLUGIN = { + TITLE: i18n.translate('xpack.licenseMgmt.managementSectionDisplayName', { + defaultMessage: 'License Management', + }), + ID: 'license_management', +}; diff --git a/x-pack/legacy/plugins/license_management/index.js b/x-pack/legacy/plugins/license_management/index.js deleted file mode 100644 index 1c69b5d96aad6b..00000000000000 --- a/x-pack/legacy/plugins/license_management/index.js +++ /dev/null @@ -1,38 +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 { resolve } from 'path'; -import { PLUGIN } from './common/constants'; -import { - registerLicenseRoute, - registerStartTrialRoutes, - registerStartBasicRoute, - registerPermissionsRoute -} from './server/routes/api/license/'; -import { createRouter } from '../../server/lib/create_router'; - -export function licenseManagement(kibana) { - return new kibana.Plugin({ - id: PLUGIN.ID, - configPrefix: 'xpack.license_management', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch'], - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: [ - 'plugins/license_management', - ] - }, - init: (server) => { - const xpackInfo = server.plugins.xpack_main.info; - const router = createRouter(server, PLUGIN.ID, '/api/license'); - registerLicenseRoute(router, xpackInfo); - registerStartTrialRoutes(router, xpackInfo); - registerStartBasicRoute(router, xpackInfo); - registerPermissionsRoute(router, xpackInfo); - } - }); -} diff --git a/x-pack/legacy/plugins/license_management/index.ts b/x-pack/legacy/plugins/license_management/index.ts new file mode 100644 index 00000000000000..c621a96945c41d --- /dev/null +++ b/x-pack/legacy/plugins/license_management/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { resolve } from 'path'; +import { PLUGIN } from './common/constants'; +import { Legacy } from '../../../../kibana'; +import { plugin } from './server/np_ready'; + +export function licenseManagement(kibana: any) { + return new kibana.Plugin({ + id: PLUGIN.ID, + configPrefix: 'xpack.license_management', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch'], + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/np_ready/application/index.scss'), + managementSections: ['plugins/license_management/legacy'], + }, + init: (server: Legacy.Server) => { + plugin({} as any).setup(server.newPlatform.setup.core, { + ...server.newPlatform.setup.plugins, + __LEGACY: { + xpackMain: server.plugins.xpack_main, + elasticsearch: server.plugins.elasticsearch, + }, + }); + }, + }); +} diff --git a/x-pack/legacy/plugins/license_management/public/index.js b/x-pack/legacy/plugins/license_management/public/legacy.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/public/index.js rename to x-pack/legacy/plugins/license_management/public/legacy.ts diff --git a/x-pack/legacy/plugins/license_management/public/lib/es.js b/x-pack/legacy/plugins/license_management/public/lib/es.js deleted file mode 100644 index 24dbf659ae657d..00000000000000 --- a/x-pack/legacy/plugins/license_management/public/lib/es.js +++ /dev/null @@ -1,71 +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 chrome from 'ui/chrome'; - -import $ from 'jquery'; - -export function putLicense(license, acknowledge) { - const options = { - url: `${chrome.addBasePath('/api/license')}${acknowledge ? '?acknowledge=true' : ''}`, - data: license, - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'PUT', - }; - - return $.ajax(options); -} - -export function startBasic(acknowledge) { - const options = { - url: `${chrome.addBasePath('/api/license/start_basic')}${acknowledge ? '?acknowledge=true' : ''}`, - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'POST', - }; - - return $.ajax(options); -} - -export function startTrial() { - const options = { - url: chrome.addBasePath('/api/license/start_trial'), - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'POST', - }; - - return $.ajax(options); -} - -export function canStartTrial() { - const options = { - url: chrome.addBasePath('/api/license/start_trial'), - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'GET', - }; - - return $.ajax(options); -} - -export function getPermissions() { - const options = { - url: chrome.addBasePath('/api/license/permissions'), - contentType: 'application/json', - cache: false, - crossDomain: true, - type: 'POST', - }; - - return $.ajax(options); -} - diff --git a/x-pack/legacy/plugins/license_management/public/main.html b/x-pack/legacy/plugins/license_management/public/main.html deleted file mode 100644 index 310eda8be763a0..00000000000000 --- a/x-pack/legacy/plugins/license_management/public/main.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/x-pack/legacy/plugins/license_management/public/management_section.js b/x-pack/legacy/plugins/license_management/public/management_section.ts similarity index 62% rename from x-pack/legacy/plugins/license_management/public/management_section.js rename to x-pack/legacy/plugins/license_management/public/management_section.ts index 8f5cc8a2226dc0..4f69581fff0df6 100644 --- a/x-pack/legacy/plugins/license_management/public/management_section.js +++ b/x-pack/legacy/plugins/license_management/public/management_section.ts @@ -5,15 +5,11 @@ */ import { management } from 'ui/management'; -import { BASE_PATH } from '../common/constants'; -import { i18n } from '@kbn/i18n'; +import { BASE_PATH, PLUGIN } from '../common/constants'; management.getSection('elasticsearch').register('license_management', { visible: true, - display: i18n.translate('xpack.licenseMgmt.managementSectionDisplayName', { - defaultMessage: 'License Management', - }), + display: PLUGIN.TITLE, order: 99, - url: `#${BASE_PATH}home` + url: `#${BASE_PATH}home`, }); - diff --git a/x-pack/legacy/plugins/license_management/public/_license_management.scss b/x-pack/legacy/plugins/license_management/public/np_ready/application/_license_management.scss similarity index 100% rename from x-pack/legacy/plugins/license_management/public/_license_management.scss rename to x-pack/legacy/plugins/license_management/public/np_ready/application/_license_management.scss diff --git a/x-pack/legacy/plugins/license_management/public/app.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.container.js similarity index 94% rename from x-pack/legacy/plugins/license_management/public/app.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/app.container.js index eac12710aeedc8..ab81f7ff14edd8 100644 --- a/x-pack/legacy/plugins/license_management/public/app.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.container.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { App as PresentationComponent } from './app'; -import { getPermission, isPermissionsLoading, getPermissionsError } from './store/reducers/licenseManagement'; +import { getPermission, isPermissionsLoading, getPermissionsError } from './store/reducers/license_management'; import { loadPermissions } from './store/actions/permissions'; const mapStateToProps = state => { diff --git a/x-pack/legacy/plugins/license_management/public/app.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.js similarity index 90% rename from x-pack/legacy/plugins/license_management/public/app.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/app.js index ff3a2d6196d761..70e35e0bd82983 100644 --- a/x-pack/legacy/plugins/license_management/public/app.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/app.js @@ -6,9 +6,9 @@ import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { LicenseDashboard, UploadLicense } from './sections/'; +import { LicenseDashboard, UploadLicense } from './sections'; import { Switch, Route } from 'react-router-dom'; -import { BASE_PATH, APP_PERMISSION } from '../common/constants'; +import { APP_PERMISSION } from '../../../common/constants'; import { EuiPageBody, EuiEmptyPrompt, EuiText, EuiLoadingSpinner, EuiCallOut } from '@elastic/eui'; export class App extends Component { @@ -84,8 +84,10 @@ export class App extends Component { return ( - - + + + {/* Match all */} + ); diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx b/x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx new file mode 100644 index 00000000000000..d0bf5deacfac10 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/boot.tsx @@ -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 React from 'react'; +import { Provider } from 'react-redux'; +import { HashRouter } from 'react-router-dom'; +import { render, unmountComponentAtNode } from 'react-dom'; +import * as history from 'history'; +import { DocLinksStart, HttpSetup, ToastsSetup, ChromeStart } from 'src/core/public'; + +// @ts-ignore +import { App } from './app.container'; +// @ts-ignore +import { licenseManagementStore } from './store'; + +import { setDocLinks } from './lib/docs_links'; +import { BASE_PATH } from '../../../common/constants'; +import { Breadcrumb } from './breadcrumbs'; + +interface AppDependencies { + element: HTMLElement; + chrome: ChromeStart; + + I18nContext: any; + legacy: { + xpackInfo: any; + refreshXpack: () => void; + MANAGEMENT_BREADCRUMB: Breadcrumb; + }; + + toasts: ToastsSetup; + docLinks: DocLinksStart; + http: HttpSetup; +} + +export const boot = (deps: AppDependencies) => { + const { I18nContext, element, legacy, toasts, docLinks, http, chrome } = deps; + const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; + const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; + const securityDocumentationLink = `${esBase}/security-settings.html`; + + const initialState = { license: legacy.xpackInfo.get('license') }; + + setDocLinks({ securityDocumentationLink }); + + const services = { + legacy: { + refreshXpack: legacy.refreshXpack, + xPackInfo: legacy.xpackInfo, + }, + // So we can imperatively control the hash route + history: history.createHashHistory({ basename: BASE_PATH }), + toasts, + http, + chrome, + MANAGEMENT_BREADCRUMB: legacy.MANAGEMENT_BREADCRUMB, + }; + + const store = licenseManagementStore(initialState, services); + render( + + + + + + + , + element + ); + + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/license_management/public/breadcrumbs.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/breadcrumbs.ts similarity index 52% rename from x-pack/legacy/plugins/license_management/public/breadcrumbs.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/breadcrumbs.ts index 0de04f4bd70fc1..2da04b22c03868 100644 --- a/x-pack/legacy/plugins/license_management/public/breadcrumbs.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/breadcrumbs.ts @@ -5,28 +5,33 @@ */ import { i18n } from '@kbn/i18n'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { BASE_PATH } from '../common/constants'; -export function getDashboardBreadcrumbs() { +import { BASE_PATH } from '../../../common/constants'; + +export interface Breadcrumb { + text: string; + href: string; +} + +export function getDashboardBreadcrumbs(root: Breadcrumb) { return [ - MANAGEMENT_BREADCRUMB, + root, { text: i18n.translate('xpack.licenseMgmt.dashboard.breadcrumb', { - defaultMessage: 'License management' + defaultMessage: 'License management', }), - href: `#${BASE_PATH}home` - } + href: `#${BASE_PATH}home`, + }, ]; } -export function getUploadBreadcrumbs() { +export function getUploadBreadcrumbs(root: Breadcrumb) { return [ - ...getDashboardBreadcrumbs(), + ...getDashboardBreadcrumbs(root), { text: i18n.translate('xpack.licenseMgmt.upload.breadcrumb', { - defaultMessage: 'Upload' - }) - } + defaultMessage: 'Upload', + }), + }, ]; } diff --git a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/index.js diff --git a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/telemetry_opt_in.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/components/telemetry_opt_in/telemetry_opt_in.js diff --git a/x-pack/legacy/plugins/license_management/public/index.scss b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.scss similarity index 90% rename from x-pack/legacy/plugins/license_management/public/index.scss rename to x-pack/legacy/plugins/license_management/public/np_ready/application/index.scss index 1887a9b48f3659..4fb8aafcca93cf 100644 --- a/x-pack/legacy/plugins/license_management/public/index.scss +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.scss @@ -10,4 +10,4 @@ // licChart__legend--small // licChart__legend-isLoading -@import 'license_management'; \ No newline at end of file +@import 'license_management'; diff --git a/x-pack/legacy/plugins/license_management/common/constants/plugin.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.ts similarity index 81% rename from x-pack/legacy/plugins/license_management/common/constants/plugin.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/index.ts index bab1345a33e7df..1f963d7f8fcce2 100644 --- a/x-pack/legacy/plugins/license_management/common/constants/plugin.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN = { - ID: 'license_management', -}; +export * from './boot'; diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_license_route.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts similarity index 55% rename from x-pack/legacy/plugins/license_management/server/routes/api/license/register_license_route.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts index 8e66ff4891643c..761fcd2674df64 100644 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_license_route.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/docs_links.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { putLicense } from '../../../lib/license'; +let docLinks: Record = {}; -export function registerLicenseRoute(router, xpackInfo) { - router.put('', (request) => { - return putLicense(request, xpackInfo); - }); -} +export const setDocLinks = (links: Record) => { + docLinks = links; +}; + +export const getDocLinks = () => docLinks; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.ts new file mode 100644 index 00000000000000..3924de2202d515 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/es.ts @@ -0,0 +1,62 @@ +/* + * 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 { HttpSetup } from 'src/core/public'; + +const BASE_PATH = '/api/license'; + +export function putLicense(http: HttpSetup, license: string, acknowledge: boolean) { + return http.put(BASE_PATH, { + query: { + acknowledge: acknowledge ? 'true' : '', + }, + body: license, + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} + +export function startBasic(http: HttpSetup, acknowledge: boolean) { + return http.post(`${BASE_PATH}/start_basic`, { + query: { + acknowledge: acknowledge ? 'true' : '', + }, + headers: { + contentType: 'application/json', + }, + body: null, + cache: 'no-cache', + }); +} + +export function startTrial(http: HttpSetup) { + return http.post(`${BASE_PATH}/start_trial`, { + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} + +export function canStartTrial(http: HttpSetup) { + return http.get(`${BASE_PATH}/start_trial`, { + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} + +export function getPermissions(http: HttpSetup) { + return http.post(`${BASE_PATH}/permissions`, { + headers: { + contentType: 'application/json', + }, + cache: 'no-cache', + }); +} diff --git a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/telemetry.js similarity index 68% rename from x-pack/legacy/plugins/license_management/public/lib/telemetry.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/lib/telemetry.js index 61d0322227d8ef..220807c2a0ac4f 100644 --- a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/lib/telemetry.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fetchTelemetry } from '../../../../../../src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry'; -export { PRIVACY_STATEMENT_URL } from '../../../../../../src/legacy/core_plugins/telemetry/common/constants'; -export { TelemetryOptInProvider } from '../../../../../../src/legacy/core_plugins/telemetry/public/services'; -export { OptInExampleFlyout } from '../../../../../../src/legacy/core_plugins/telemetry/public/components'; +import { fetchTelemetry } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/hacks/fetch_telemetry'; +export { PRIVACY_STATEMENT_URL } from '../../../../../../../../src/legacy/core_plugins/telemetry/common/constants'; +export { TelemetryOptInProvider } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/services'; +export { OptInExampleFlyout } from '../../../../../../../../src/legacy/core_plugins/telemetry/public/components'; let telemetryEnabled; let httpClient; diff --git a/x-pack/legacy/plugins/license_management/public/sections/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/add_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license.js similarity index 94% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/add_license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license.js index 3c0402f974f067..bd876ff89b1adf 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/add_license.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import { BASE_PATH } from '../../../../common/constants'; +import { BASE_PATH } from '../../../../../../common/constants'; import { EuiCard, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/add_license/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/add_license/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js similarity index 78% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js index 96c727969e2c29..b8db24e65e3bd8 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/index.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { LicenseDashboard } from './license_dashboard'; +export { LicenseDashboard } from './license_dashboard.container'; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js new file mode 100644 index 00000000000000..4073c5cd52fbb5 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.container.js @@ -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 { connect } from 'react-redux'; +import { LicenseDashboard as PresentationComponent } from './license_dashboard'; +import { setBreadcrumb } from '../../store/actions/set_breadcrumb'; + +const mapDispatchToProps = { + setBreadcrumb, +}; + +export const LicenseDashboard = connect(null, mapDispatchToProps)(PresentationComponent); diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_dashboard.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.js similarity index 88% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_dashboard.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.js index 4b74211fe83905..e24c167b3b5ad5 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_dashboard.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_dashboard.js @@ -18,7 +18,8 @@ import { EuiSpacer } from '@elastic/eui'; -export const LicenseDashboard = () => { +export const LicenseDashboard = ({ setBreadcrumb } = { setBreadcrumb: () => {} }) => { + setBreadcrumb('dashboard'); return (
diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.container.js similarity index 95% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.container.js index a282fdc10ddfbb..07847a027452f0 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.container.js @@ -6,7 +6,7 @@ import { LicenseStatus as PresentationComponent } from './license_status'; import { connect } from 'react-redux'; -import { getLicense, getExpirationDateFormatted, isExpired } from '../../../store/reducers/licenseManagement'; +import { getLicense, getExpirationDateFormatted, isExpired } from '../../../store/reducers/license_management'; import { i18n } from '@kbn/i18n'; const mapStateToProps = (state) => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js similarity index 92% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js index 96348d4fcb1469..e01bcdee2fae61 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.container.js @@ -9,7 +9,7 @@ import { connect } from 'react-redux'; import { RequestTrialExtension as PresentationComponent } from './request_trial_extension'; import { shouldShowRequestTrialExtension -} from '../../../store/reducers/licenseManagement'; +} from '../../../store/reducers/license_management'; const mapStateToProps = state => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js similarity index 96% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js index 372dd5ba5afea1..106b1a0d24aecc 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/request_trial_extension/request_trial_extension.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js @@ -8,7 +8,7 @@ import React from 'react'; import { EuiFlexItem, EuiCard, EuiLink, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EXTERNAL_LINKS } from '../../../../common/constants'; +import { EXTERNAL_LINKS } from '../../../../../../common/constants'; export const RequestTrialExtension = ({ shouldShowRequestTrialExtension }) => { if (!shouldShowRequestTrialExtension) { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js similarity index 95% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js index bd7f7c28be26f9..2bb67835c9c310 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.container.js @@ -12,7 +12,7 @@ import { getLicenseType, shouldShowRevertToBasicLicense, getStartBasicMessages -} from '../../../store/reducers/licenseManagement'; +} from '../../../store/reducers/license_management'; import { startBasicLicense, cancelStartBasicLicense } from '../../../store/actions/start_basic'; const mapStateToProps = state => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js similarity index 98% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js index 32ab337359a9ba..0e3cea9982a9b8 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/revert_to_basic/revert_to_basic.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js @@ -16,7 +16,7 @@ import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EXTERNAL_LINKS } from '../../../../common/constants'; +import { EXTERNAL_LINKS } from '../../../../../../common/constants'; export class RevertToBasic extends React.PureComponent { cancel = () => { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/index.js diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.container.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.container.js similarity index 97% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.container.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.container.js index d8b71688fd5377..41a60e38c0932b 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.container.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.container.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { StartTrial as PresentationComponent } from './start_trial'; import { loadTrialStatus, startLicenseTrial } from '../../../store/actions/start_trial'; -import { shouldShowStartTrial } from '../../../store/reducers/licenseManagement'; +import { shouldShowStartTrial } from '../../../store/reducers/license_management'; const mapStateToProps = (state) => { return { diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js similarity index 96% rename from x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js index 20b130d80a211b..4cbd64bdea31fb 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js @@ -22,13 +22,11 @@ import { EuiModalHeaderTitle } from '@elastic/eui'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import { TelemetryOptIn } from '../../../components/telemetry_opt_in'; import { optInToTelemetry } from '../../../lib/telemetry'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EXTERNAL_LINKS } from '../../../../common/constants'; -const esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; -const securityDocumentationLink = `${esBase}/security-settings.html`; +import { EXTERNAL_LINKS } from '../../../../../../common/constants'; +import { getDocLinks } from '../../../lib/docs_links'; export class StartTrial extends React.PureComponent { @@ -134,7 +132,7 @@ export class StartTrial extends React.PureComponent { authenticationTypeList: 'AD/LDAP, SAML, PKI, SAML/SSO', securityDocumentationLinkText: ( { @@ -148,16 +149,20 @@ export class UploadLicense extends React.PureComponent { - - } - onChange={this.handleFile} - /> - + + + + } + onChange={this.handleFile} + /> + + + { diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/add_error_message.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_error_message.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/actions/add_error_message.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_error_message.js diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/add_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_license.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/actions/add_license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/add_license.js diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/actions/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/index.js diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/permissions.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/permissions.js similarity index 86% rename from x-pack/legacy/plugins/license_management/public/store/actions/permissions.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/permissions.js index b9316c0a958c44..1cb55bf334a220 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/permissions.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/permissions.js @@ -19,10 +19,10 @@ export const permissionsError = createAction( 'LICENSE_MANAGEMENT_PERMISSIONS_ERROR' ); -export const loadPermissions = () => async dispatch => { +export const loadPermissions = () => async (dispatch, getState, { http }) => { dispatch(permissionsLoading(true)); try { - const permissions = await getPermissions(); + const permissions = await getPermissions(http); dispatch(permissionsLoading(false)); dispatch(permissionsSuccess(permissions.hasPermission)); } catch (e) { diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts new file mode 100644 index 00000000000000..bcb4a907bdf88c --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/set_breadcrumb.ts @@ -0,0 +1,22 @@ +/* + * 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 { ThunkAction } from 'redux-thunk'; +import { ChromeStart } from 'src/core/public'; +import { getDashboardBreadcrumbs, getUploadBreadcrumbs, Breadcrumb } from '../../breadcrumbs'; + +export const setBreadcrumb = ( + section: 'dashboard' | 'upload' +): ThunkAction => ( + dispatch, + getState, + { chrome, MANAGEMENT_BREADCRUMB } +) => { + if (section === 'upload') { + chrome.setBreadcrumbs(getUploadBreadcrumbs(MANAGEMENT_BREADCRUMB)); + } else { + chrome.setBreadcrumbs(getDashboardBreadcrumbs(MANAGEMENT_BREADCRUMB)); + } +}; diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/start_basic.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_basic.js similarity index 88% rename from x-pack/legacy/plugins/license_management/public/store/actions/start_basic.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_basic.js index b144729c1a317e..cc52916a30d8b4 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/start_basic.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_basic.js @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { createAction } from 'redux-actions'; import { startBasic } from '../../lib/es'; -import { toastNotifications } from 'ui/notify'; -import { i18n } from '@kbn/i18n'; export const startBasicLicenseStatus = createAction( 'LICENSE_MANAGEMENT_START_BASIC_LICENSE_STATUS' @@ -20,17 +19,17 @@ export const cancelStartBasicLicense = createAction( export const startBasicLicense = (currentLicenseType, ack) => async ( dispatch, getState, - { xPackInfo, $injector } + { legacy: { refreshXpack }, toasts, http } ) => { /*eslint camelcase: 0*/ - const { acknowledged, basic_was_started, error_message, acknowledge } = await startBasic(ack); + const { acknowledged, basic_was_started, error_message, acknowledge } = await startBasic(http, ack); if (acknowledged) { if (basic_was_started) { - await xPackInfo.refresh($injector); + await refreshXpack(); // reload necessary to get left nav to refresh with proper links window.location.reload(); } else { - return toastNotifications.addDanger(error_message); + return toasts.addDanger(error_message); } } else { //messages coming back in arrays diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/start_trial.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_trial.js similarity index 74% rename from x-pack/legacy/plugins/license_management/public/store/actions/start_trial.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_trial.js index 0bc274366d97e1..e3f33a0cdc9776 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/start_trial.js @@ -6,29 +6,28 @@ import { createAction } from 'redux-actions'; import { canStartTrial, startTrial } from '../../lib/es'; -import { toastNotifications } from 'ui/notify'; export const trialStatusLoaded = createAction( 'LICENSE_MANAGEMENT_TRIAL_STATUS_LOADED' ); -export const loadTrialStatus = () => async dispatch => { - const trialOK = await canStartTrial(); +export const loadTrialStatus = () => async (dispatch, getState, { http }) => { + const trialOK = await canStartTrial(http); dispatch(trialStatusLoaded(trialOK)); }; export const startLicenseTrial = () => async ( dispatch, getState, - { xPackInfo, $injector } + { legacy: { refreshXpack }, toasts, http } ) => { /*eslint camelcase: 0*/ - const { trial_was_started, error_message } = await startTrial(); + const { trial_was_started, error_message } = await startTrial(http); if (trial_was_started) { - await xPackInfo.refresh($injector); + await refreshXpack(); // reload necessary to get left nav to refresh with proper links window.location.reload(); } else { - return toastNotifications.addDanger(error_message); + return toasts.addDanger(error_message); } }; diff --git a/x-pack/legacy/plugins/license_management/public/store/actions/upload_license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js similarity index 90% rename from x-pack/legacy/plugins/license_management/public/store/actions/upload_license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js index be5b3125a6a122..e93e891f6de93f 100644 --- a/x-pack/legacy/plugins/license_management/public/store/actions/upload_license.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/actions/upload_license.js @@ -5,8 +5,7 @@ */ import { createAction } from 'redux-actions'; -import { addLicense } from '../actions/add_license'; -import { BASE_PATH } from '../../../common/constants/base_path'; +import { addLicense } from './add_license'; import { putLicense } from '../../lib/es'; import { addUploadErrorMessage } from './add_error_message'; import { i18n } from '@kbn/i18n'; @@ -17,7 +16,19 @@ const genericUploadError = i18n.translate('xpack.licenseMgmt.uploadLicense.gener defaultMessage: 'Error encountered uploading license:' }); -const dispatchFromResponse = async (response, dispatch, currentLicenseType, newLicenseType, { xPackInfo, kbnUrl, $injector }) => { +const dispatchFromResponse = async ( + response, + dispatch, + currentLicenseType, + newLicenseType, + { + history, + legacy: { + xPackInfo, + refreshXpack, + }, + }, +) => { const { error, acknowledged, license_status: licenseStatus, acknowledge } = response; if (error) { dispatch(uploadLicenseStatus({})); @@ -34,10 +45,10 @@ const dispatchFromResponse = async (response, dispatch, currentLicenseType, newL defaultMessage: 'The supplied license has expired.' }))); } else { - await xPackInfo.refresh($injector); + await refreshXpack(); dispatch(addLicense(xPackInfo.get('license'))); dispatch(uploadLicenseStatus({})); - kbnUrl.change(BASE_PATH); + history.replace('/home'); // reload necessary to get left nav to refresh with proper links window.location.reload(); } @@ -75,7 +86,7 @@ export const uploadLicense = (licenseString, currentLicenseType, acknowledge) => )); } try { - const response = await putLicense(licenseString, acknowledge); + const response = await putLicense(services.http, licenseString, acknowledge); await dispatchFromResponse(response, dispatch, currentLicenseType, newLicenseType, services); } catch (err) { const message = (err.responseJSON && err.responseJSON.error.reason) ? err.responseJSON.error.reason : i18n.translate( diff --git a/x-pack/legacy/plugins/license_management/public/store/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/index.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/index.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/index.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js similarity index 80% rename from x-pack/legacy/plugins/license_management/public/store/reducers/index.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js index 297e28a320810c..7872202272acf5 100644 --- a/x-pack/legacy/plugins/license_management/public/store/reducers/index.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licenseManagement } from './licenseManagement'; +export { licenseManagement } from './license_management'; diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/license.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/license.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/licenseManagement.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license_management.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/licenseManagement.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/license_management.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/permissions.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/permissions.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/permissions.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/permissions.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/start_basic_license_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/start_basic_license_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/start_basic_license_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/start_basic_license_status.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/trial_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/trial_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/trial_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/trial_status.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/upload_error_message.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_error_message.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/upload_error_message.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_error_message.js diff --git a/x-pack/legacy/plugins/license_management/public/store/reducers/upload_status.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_status.js similarity index 100% rename from x-pack/legacy/plugins/license_management/public/store/reducers/upload_status.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/reducers/upload_status.js diff --git a/x-pack/legacy/plugins/license_management/public/store/store.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/store.js similarity index 93% rename from x-pack/legacy/plugins/license_management/public/store/store.js rename to x-pack/legacy/plugins/license_management/public/np_ready/application/store/store.js index 87ccf4e2aba461..b3e6bfbe5db739 100644 --- a/x-pack/legacy/plugins/license_management/public/store/store.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/store/store.js @@ -7,7 +7,7 @@ import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; -import { licenseManagement } from './reducers/'; +import { licenseManagement } from './reducers'; export const licenseManagementStore = (initialState = {}, services = {}) => { const enhancers = [ applyMiddleware(thunk.withExtraArgument(services)) ]; diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/index.ts b/x-pack/legacy/plugins/license_management/public/np_ready/index.ts new file mode 100644 index 00000000000000..59e2f02d8cb526 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/index.ts @@ -0,0 +1,9 @@ +/* + * 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/public'; +import { LicenseManagementUIPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => new LicenseManagementUIPlugin(); diff --git a/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts b/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts new file mode 100644 index 00000000000000..abd658ff91db04 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/np_ready/plugin.ts @@ -0,0 +1,50 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { PLUGIN } from '../../common/constants'; +import { Breadcrumb } from './application/breadcrumbs'; + +export interface Plugins { + __LEGACY: { + xpackInfo: XPackMainPlugin; + refreshXpack: () => void; + MANAGEMENT_BREADCRUMB: Breadcrumb; + }; +} + +export class LicenseManagementUIPlugin implements Plugin { + setup({ application, notifications, http }: CoreSetup, { __LEGACY }: Plugins) { + application.register({ + id: PLUGIN.ID, + title: PLUGIN.TITLE, + async mount( + { + core: { + docLinks, + i18n: { Context: I18nContext }, + chrome, + }, + }, + { element } + ) { + const { boot } = await import('./application'); + return boot({ + legacy: { ...__LEGACY }, + I18nContext, + toasts: notifications.toasts, + docLinks, + http, + element, + chrome, + }); + }, + }); + } + start(core: CoreStart, plugins: any) {} + stop() {} +} diff --git a/x-pack/legacy/plugins/license_management/public/register_route.js b/x-pack/legacy/plugins/license_management/public/register_route.js deleted file mode 100644 index fcf1b85f95b8a8..00000000000000 --- a/x-pack/legacy/plugins/license_management/public/register_route.js +++ /dev/null @@ -1,109 +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 { render, unmountComponentAtNode } from 'react-dom'; -import { Provider } from 'react-redux'; -import { HashRouter } from 'react-router-dom'; -import { setTelemetryOptInService, setTelemetryEnabled, setHttpClient, TelemetryOptInProvider } from './lib/telemetry'; -import { I18nContext } from 'ui/i18n'; -import chrome from 'ui/chrome'; - -import { App } from './app.container'; -import { BASE_PATH } from '../common/constants/base_path'; - -import routes from 'ui/routes'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; - -import template from './main.html'; -import { licenseManagementStore } from './store'; -import { getDashboardBreadcrumbs, getUploadBreadcrumbs } from './breadcrumbs'; - -const renderReact = (elem, store) => { - render( - - - - - - - , - elem - ); -}; - -/* - This method handles the cleanup needed when route is scope is destroyed. It also prevents Angular - from destroying scope when route changes and both old route and new route are this same route. -*/ -const manageAngularLifecycle = ($scope, $route, elem) => { - const lastRoute = $route.current; - const deregister = $scope.$on('$locationChangeSuccess', () => { - const currentRoute = $route.current; - // if templates are the same we are on the same route - if (lastRoute.$$route.template === currentRoute.$$route.template) { - // update the breadcrumbs by re-running the k7Breadcrumbs function - chrome.breadcrumbs.set(currentRoute.$$route.k7Breadcrumbs($route)); - // this prevents angular from destroying scope - $route.current = lastRoute; - } - }); - $scope.$on('$destroy', () => { - deregister && deregister(); - // manually unmount component when scope is destroyed - elem && unmountComponentAtNode(elem); - }); -}; -const initializeTelemetry = ($injector) => { - const telemetryEnabled = $injector.get('telemetryEnabled'); - const Private = $injector.get('Private'); - const telemetryOptInProvider = Private(TelemetryOptInProvider); - setTelemetryOptInService(telemetryOptInProvider); - setTelemetryEnabled(telemetryEnabled); - setHttpClient($injector.get('$http')); -}; -routes - .when(`${BASE_PATH}:view?`, { - template: template, - k7Breadcrumbs($route) { - switch ($route.current.params.view) { - case 'upload_license': - return getUploadBreadcrumbs(); - default: - return getDashboardBreadcrumbs(); - } - }, - controllerAs: 'licenseManagement', - controller: class LicenseManagementController { - - constructor($injector, $rootScope, $scope, $route, kbnUrl) { - initializeTelemetry($injector); - let autoLogout = null; - /* if security is disabled, there will be no autoLogout service, - so just substitute noop function in that case */ - try { - autoLogout = $injector.get('autoLogout'); - } catch (e) { - autoLogout = () => {}; - } - - $scope.$$postDigest(() => { - const elem = document.getElementById('licenseReactRoot'); - const initialState = { license: xpackInfo.get('license') }; - const kbnUrlWrapper = { - change(url) { - kbnUrl.change(url); - $rootScope.$digest(); - } - }; - const services = { autoLogout, xPackInfo: xpackInfo, kbnUrl: kbnUrlWrapper, $injector }; - const store = licenseManagementStore(initialState, services); - renderReact(elem, store); - manageAngularLifecycle($scope, $route, elem); - }); - } - } - }); diff --git a/x-pack/legacy/plugins/license_management/public/register_route.ts b/x-pack/legacy/plugins/license_management/public/register_route.ts new file mode 100644 index 00000000000000..ad84f28c6b6d78 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/public/register_route.ts @@ -0,0 +1,98 @@ +/* + * 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 { App } from 'src/core/public'; + +/* Legacy Imports */ +import { npSetup, npStart } from 'ui/new_platform'; +import { MANAGEMENT_BREADCRUMB } from 'ui/management'; +import routes from 'ui/routes'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; + +import { plugin } from './np_ready'; + +import { + setTelemetryOptInService, + setTelemetryEnabled, + setHttpClient, + TelemetryOptInProvider, + // @ts-ignore +} from './np_ready/application/lib/telemetry'; + +import { BASE_PATH } from '../common/constants'; + +/* + This method handles the cleanup needed when route is scope is destroyed. It also prevents Angular + from destroying scope when route changes and both old route and new route are this same route. +*/ +const manageAngularLifecycle = ($scope: any, $route: any, unmount: () => void) => { + const lastRoute = $route.current; + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + // if templates are the same we are on the same route + if (lastRoute.$$route.template === currentRoute.$$route.template) { + // this prevents angular from destroying scope + $route.current = lastRoute; + } + }); + $scope.$on('$destroy', () => { + if (deregister) { + deregister(); + } + unmount(); + }); +}; + +const initializeTelemetry = ($injector: any) => { + const telemetryEnabled = $injector.get('telemetryEnabled'); + const Private = $injector.get('Private'); + const telemetryOptInProvider = Private(TelemetryOptInProvider); + setTelemetryOptInService(telemetryOptInProvider); + setTelemetryEnabled(telemetryEnabled); + setHttpClient($injector.get('$http')); +}; + +const template = ` +
+
`; + +routes.when(`${BASE_PATH}:view?`, { + template, + controllerAs: 'licenseManagement', + controller: class LicenseManagementController { + constructor($injector: any, $rootScope: any, $scope: any, $route: any) { + initializeTelemetry($injector); + + $scope.$$postDigest(() => { + const element = document.getElementById('licenseReactRoot')!; + + const refreshXpack = async () => { + await xpackInfo.refresh($injector); + }; + + plugin({} as any).setup( + { + ...npSetup.core, + application: { + ...npSetup.core.application, + async register(app: App) { + const unmountApp = await app.mount({ ...npStart } as any, { + element, + appBasePath: '', + }); + manageAngularLifecycle($scope, $route, unmountApp as any); + }, + }, + }, + { + __LEGACY: { xpackInfo, refreshXpack, MANAGEMENT_BREADCRUMB }, + } + ); + }); + } + } as any, +} as any); diff --git a/x-pack/legacy/plugins/license_management/server/lib/license.js b/x-pack/legacy/plugins/license_management/server/lib/license.js deleted file mode 100644 index bc52fe7f56b34c..00000000000000 --- a/x-pack/legacy/plugins/license_management/server/lib/license.js +++ /dev/null @@ -1,27 +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. - */ - -const getLicensePath = (acknowledge) => `/_license${ acknowledge ? '?acknowledge=true' : ''}`; - -export async function putLicense(req, xpackInfo) { - const { acknowledge } = req.query; - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'POST', - path: getLicensePath(acknowledge), - body: req.payload - }; - try { - const response = await callWithRequest(req, 'transport.request', options); - const { acknowledged, license_status: licenseStatus } = response; - if (acknowledged && licenseStatus === 'valid') { - await xpackInfo.refreshNow(); - } - return response; - } catch (error) { - return error.body; - } -} diff --git a/x-pack/legacy/plugins/license_management/server/lib/start_basic.js b/x-pack/legacy/plugins/license_management/server/lib/start_basic.js deleted file mode 100644 index 0924146b702804..00000000000000 --- a/x-pack/legacy/plugins/license_management/server/lib/start_basic.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const getStartBasicPath = (acknowledge) => `/_license/start_basic${ acknowledge ? '?acknowledge=true' : ''}`; - - -export async function startBasic(req, xpackInfo) { - const { acknowledge } = req.query; - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'POST', - path: getStartBasicPath(acknowledge) - }; - try { - const response = await callWithRequest(req, 'transport.request', options); - /*eslint camelcase: 0*/ - const { basic_was_started } = response; - if (basic_was_started) { - await xpackInfo.refreshNow(); - } - return response; - } catch (error) { - return error.body; - } -} diff --git a/x-pack/legacy/plugins/license_management/server/lib/start_trial.js b/x-pack/legacy/plugins/license_management/server/lib/start_trial.js deleted file mode 100644 index 19b702e4e43c4d..00000000000000 --- a/x-pack/legacy/plugins/license_management/server/lib/start_trial.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export async function canStartTrial(req) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'GET', - path: '/_license/trial_status' - }; - try { - const response = await callWithRequest(req, 'transport.request', options); - const { eligible_to_start_trial } = response; - return eligible_to_start_trial; - } catch (error) { - return error.body; - } -} -export async function startTrial(req, xpackInfo) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); - const options = { - method: 'POST', - path: '/_license/start_trial?acknowledge=true' - }; - try { - /*eslint camelcase: 0*/ - const response = await callWithRequest(req, 'transport.request', options); - const { trial_was_started } = response; - if (trial_was_started) { - await xpackInfo.refreshNow(); - } - return response; - } catch (error) { - return error.body; - } -} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_basic_route.js b/x-pack/legacy/plugins/license_management/server/np_ready/index.ts similarity index 53% rename from x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_basic_route.js rename to x-pack/legacy/plugins/license_management/server/np_ready/index.ts index fd7bfcf8d7c2a6..2ad4143a947304 100644 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_basic_route.js +++ b/x-pack/legacy/plugins/license_management/server/np_ready/index.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { startBasic } from '../../../lib/start_basic'; +import { PluginInitializerContext } from 'src/core/server'; +import { LicenseManagementServerPlugin } from './plugin'; -export function registerStartBasicRoute(router, xpackInfo) { - router.post('/start_basic', (request) => { - return startBasic(request, xpackInfo); - }); -} +export const plugin = (ctx: PluginInitializerContext) => new LicenseManagementServerPlugin(); diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/lib/license.ts b/x-pack/legacy/plugins/license_management/server/np_ready/lib/license.ts new file mode 100644 index 00000000000000..b52c9d50170b9b --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/license.ts @@ -0,0 +1,33 @@ +/* + * 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 'src/core/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; +const getLicensePath = (acknowledge: boolean) => + `/_license${acknowledge ? '?acknowledge=true' : ''}`; + +export async function putLicense( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { + const { acknowledge } = req.query; + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'POST', + path: getLicensePath(Boolean(acknowledge)), + body: req.body, + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + const { acknowledged, license_status: licenseStatus } = response; + if (acknowledged && licenseStatus === 'valid') { + await xpackInfo.refreshNow(); + } + return response; + } catch (error) { + return error.body; + } +} diff --git a/x-pack/legacy/plugins/license_management/server/lib/permissions.js b/x-pack/legacy/plugins/license_management/server/np_ready/lib/permissions.ts similarity index 59% rename from x-pack/legacy/plugins/license_management/server/lib/permissions.js rename to x-pack/legacy/plugins/license_management/server/np_ready/lib/permissions.ts index 11d25a9c2dc24f..84cd92821797f2 100644 --- a/x-pack/legacy/plugins/license_management/server/lib/permissions.js +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/permissions.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { wrapCustomError } from '../../../../server/lib/create_router/error_wrappers'; - -export async function getPermissions(req, xpackInfo) { - if (!xpackInfo) { - // xpackInfo is updated via poll, so it may not be available until polling has begun. - // In this rare situation, tell the client the service is temporarily unavailable. - throw wrapCustomError(new Error('Security info unavailable'), 503); - } +import { KibanaRequest } from 'src/core/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; +export async function getPermissions( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { const securityInfo = xpackInfo && xpackInfo.isAvailable() && xpackInfo.feature('security'); if (!securityInfo || !securityInfo.isAvailable() || !securityInfo.isEnabled()) { // If security isn't enabled, let the user use license management @@ -21,22 +20,21 @@ export async function getPermissions(req, xpackInfo) { }; } - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); + const { callWithRequest } = elasticsearch.getCluster('admin'); const options = { method: 'POST', path: '/_security/user/_has_privileges', body: { cluster: ['manage'], // License management requires "manage" cluster privileges - } + }, }; try { - const response = await callWithRequest(req, 'transport.request', options); + const response = await callWithRequest(req as any, 'transport.request', options); return { hasPermission: response.cluster.manage, }; } catch (error) { return error.body; } - } diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.ts b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.ts new file mode 100644 index 00000000000000..ba042be132d68e --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_basic.ts @@ -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 { KibanaRequest } from 'kibana/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; + +const getStartBasicPath = (acknowledge: boolean) => + `/_license/start_basic${acknowledge ? '?acknowledge=true' : ''}`; + +export async function startBasic( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { + const { acknowledge } = req.query; + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'POST', + path: getStartBasicPath(Boolean(acknowledge)), + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + const { basic_was_started: basicWasStarted } = response; + if (basicWasStarted) { + await xpackInfo.refreshNow(); + } + return response; + } catch (error) { + return error.body; + } +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.ts b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.ts new file mode 100644 index 00000000000000..3569085d413ca4 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/lib/start_trial.ts @@ -0,0 +1,47 @@ +/* + * 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 'src/core/server'; +import { ElasticsearchPlugin } from '../../../../../../../src/legacy/core_plugins/elasticsearch'; + +export async function canStartTrial( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin +) { + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'GET', + path: '/_license/trial_status', + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + return response.eligible_to_start_trial; + } catch (error) { + return error.body; + } +} + +export async function startTrial( + req: KibanaRequest, + elasticsearch: ElasticsearchPlugin, + xpackInfo: any +) { + const { callWithRequest } = elasticsearch.getCluster('admin'); + const options = { + method: 'POST', + path: '/_license/start_trial?acknowledge=true', + }; + try { + const response = await callWithRequest(req as any, 'transport.request', options); + const { trial_was_started: trialWasStarted } = response; + if (trialWasStarted) { + await xpackInfo.refreshNow(); + } + return response; + } catch (error) { + return error.body; + } +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/plugin.ts b/x-pack/legacy/plugins/license_management/server/np_ready/plugin.ts new file mode 100644 index 00000000000000..9f065cf98d715f --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/plugin.ts @@ -0,0 +1,35 @@ +/* + * 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/server'; +import { Dependencies, Server } from './types'; + +import { + registerLicenseRoute, + registerStartTrialRoutes, + registerStartBasicRoute, + registerPermissionsRoute, +} from './routes/api/license'; + +export class LicenseManagementServerPlugin implements Plugin { + setup({ http }: CoreSetup, { __LEGACY }: Dependencies) { + const xpackInfo = __LEGACY.xpackMain.info; + const router = http.createRouter(); + + const server: Server = { + router, + }; + + const legacy = { plugins: __LEGACY }; + + registerLicenseRoute(server, legacy, xpackInfo); + registerStartTrialRoutes(server, legacy, xpackInfo); + registerStartBasicRoute(server, legacy, xpackInfo); + registerPermissionsRoute(server, legacy, xpackInfo); + } + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/index.js b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/index.ts similarity index 100% rename from x-pack/legacy/plugins/license_management/server/routes/api/license/index.js rename to x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/index.ts diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts new file mode 100644 index 00000000000000..cdc929a2f3bb38 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_license_route.ts @@ -0,0 +1,32 @@ +/* + * 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 { putLicense } from '../../../lib/license'; +import { Legacy, Server } from '../../../types'; + +export function registerLicenseRoute(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.put( + { + path: '/api/license', + validate: { + query: schema.object({ acknowledge: schema.string() }), + body: schema.object({ + license: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + async (ctx, request, response) => { + try { + return response.ok({ + body: await putLicense(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.ts new file mode 100644 index 00000000000000..0f6c343d04fcd0 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_permissions_route.ts @@ -0,0 +1,29 @@ +/* + * 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 { getPermissions } from '../../../lib/permissions'; +import { Legacy, Server } from '../../../types'; + +export function registerPermissionsRoute(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.post( + { path: '/api/license/permissions', validate: false }, + async (ctx, request, response) => { + if (!xpackInfo) { + // xpackInfo is updated via poll, so it may not be available until polling has begun. + // In this rare situation, tell the client the service is temporarily unavailable. + return response.customError({ statusCode: 503, body: 'Security info unavailable' }); + } + + try { + return response.ok({ + body: await getPermissions(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.ts new file mode 100644 index 00000000000000..ee7ac8602104be --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_basic_route.ts @@ -0,0 +1,27 @@ +/* + * 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 { startBasic } from '../../../lib/start_basic'; +import { Legacy, Server } from '../../../types'; + +export function registerStartBasicRoute(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.post( + { + path: '/api/license/start_basic', + validate: { query: schema.object({ acknowledge: schema.string() }) }, + }, + async (ctx, request, response) => { + try { + return response.ok({ + body: await startBasic(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.ts b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.ts new file mode 100644 index 00000000000000..d93f13eba363a3 --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/routes/api/license/register_start_trial_routes.ts @@ -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 { canStartTrial, startTrial } from '../../../lib/start_trial'; +import { Legacy, Server } from '../../../types'; + +export function registerStartTrialRoutes(server: Server, legacy: Legacy, xpackInfo: any) { + server.router.get( + { path: '/api/license/start_trial', validate: false }, + async (ctx, request, response) => { + try { + return response.ok({ body: await canStartTrial(request, legacy.plugins.elasticsearch) }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); + + server.router.post( + { path: '/api/license/start_trial', validate: false }, + async (ctx, request, response) => { + try { + return response.ok({ + body: await startTrial(request, legacy.plugins.elasticsearch, xpackInfo), + }); + } catch (e) { + return response.internalError({ body: e }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/license_management/server/np_ready/types.ts b/x-pack/legacy/plugins/license_management/server/np_ready/types.ts new file mode 100644 index 00000000000000..a636481323e29f --- /dev/null +++ b/x-pack/legacy/plugins/license_management/server/np_ready/types.ts @@ -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. + */ + +import { IRouter } from 'src/core/server'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { ElasticsearchPlugin } from '../../../../../../src/legacy/core_plugins/elasticsearch'; + +export interface Dependencies { + __LEGACY: { + xpackMain: XPackMainPlugin; + elasticsearch: ElasticsearchPlugin; + }; +} + +export interface Server { + router: IRouter; +} + +export interface Legacy { + plugins: Dependencies['__LEGACY']; +} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js b/x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js deleted file mode 100644 index d8fd4e5abd8f20..00000000000000 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_permissions_route.js +++ /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 { getPermissions } from '../../../lib/permissions'; - -export function registerPermissionsRoute(router, xpackInfo) { - router.post('/permissions', (request) => { - return getPermissions(request, xpackInfo); - }); -} diff --git a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js b/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js deleted file mode 100644 index c13ea680fa0da3..00000000000000 --- a/x-pack/legacy/plugins/license_management/server/routes/api/license/register_start_trial_routes.js +++ /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 { canStartTrial, startTrial } from '../../../lib/start_trial'; - -export function registerStartTrialRoutes(router, xpackInfo) { - router.get('/start_trial', (request) => { - return canStartTrial(request); - }); - router.post('/start_trial', (request) => { - return startTrial(request, xpackInfo); - }); -} diff --git a/x-pack/legacy/plugins/xpack_main/public/components/index.js b/x-pack/legacy/plugins/xpack_main/public/components/index.js index c18cf4665f4ac9..16fdc9e7a63a70 100644 --- a/x-pack/legacy/plugins/xpack_main/public/components/index.js +++ b/x-pack/legacy/plugins/xpack_main/public/components/index.js @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -export { LicenseStatus } from '../../../license_management/public/sections/license_dashboard/license_status/license_status'; -export { AddLicense } from '../../../license_management/public/sections/license_dashboard/add_license/add_license'; +export { + LicenseStatus +} from '../../../license_management/public/np_ready/application/sections/license_dashboard/license_status/license_status'; + +export { AddLicense } from '../../../license_management/public/np_ready/application/sections/license_dashboard/add_license/add_license'; /* * For to link to management From 43c55cf6a4074ef5c4e6767ac66ef29644aa41f4 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Mon, 9 Dec 2019 18:09:44 +0200 Subject: [PATCH 06/36] Move apply filters action to NP (#52156) * move action * attach action in start * Remove uiActions from core_plugins/data * Don't export apply filters popup * import DataPublicPlugin after all other deps have loaded * lint * Remove unused import --- src/core/MIGRATION.md | 2 +- src/legacy/core_plugins/data/public/legacy.ts | 1 - src/legacy/core_plugins/data/public/plugin.ts | 20 +---------- src/plugins/data/kibana.json | 3 +- .../public/actions}/apply_filter_action.ts | 23 +++++-------- src/plugins/data/public/actions/index.ts | 20 +++++++++++ src/plugins/data/public/index.ts | 8 ++--- src/plugins/data/public/plugin.ts | 34 ++++++++++++++----- src/plugins/data/public/services.ts | 8 +++++ src/plugins/data/public/types.ts | 9 +++++ src/plugins/data/public/ui/index.ts | 1 - 11 files changed, 78 insertions(+), 51 deletions(-) rename src/{legacy/core_plugins/data/public/filter/action => plugins/data/public/actions}/apply_filter_action.ts (84%) create mode 100644 src/plugins/data/public/actions/index.ts diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 84b99b034af99a..2527ffba2cbbd5 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1193,7 +1193,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | Legacy Platform | New Platform | Notes | | ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `import 'ui/apply_filters'` | `import { applyFiltersPopover } from '../data/public'` | Directive is deprecated. | +| `import 'ui/apply_filters'` | N/A. Replaced by triggering an APPLY_FILTER_TRIGGER trigger. | Directive is deprecated. | | `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | Directive is deprecated. | | `import 'ui/query_bar'` | `import { QueryStringInput } from '../data/public'` | Directives are deprecated. | | `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts index b1d838aed992da..a6646ea338c93c 100644 --- a/src/legacy/core_plugins/data/public/legacy.ts +++ b/src/legacy/core_plugins/data/public/legacy.ts @@ -43,5 +43,4 @@ export const setup = dataPlugin.setup(npSetup.core); export const start = dataPlugin.start(npStart.core, { data: npStart.plugins.data, - uiActions: npSetup.plugins.uiActions, }); diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 2a7bd5476f0a5c..a4fdfd7482f741 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -22,19 +22,12 @@ import { createSearchBar, StatetfulSearchBarProps } from './search'; import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; import { initLegacyModule } from './shim/legacy_module'; -import { IUiActionsSetup } from '../../../../plugins/ui_actions/public'; -import { - createFilterAction, - GLOBAL_APPLY_FILTER_ACTION, -} from './filter/action/apply_filter_action'; -import { APPLY_FILTER_TRIGGER } from '../../../../plugins/embeddable/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { setFieldFormats } from '../../../../plugins/data/public/services'; export interface DataPluginStartDependencies { data: DataPublicPluginStart; - uiActions: IUiActionsSetup; } /** @@ -67,7 +60,7 @@ export class DataPlugin implements Plugin { return createAction({ type: GLOBAL_APPLY_FILTER_ACTION, @@ -75,12 +68,12 @@ export function createFilterAction( if (selectedFilters.length > 1) { const indexPatterns = await Promise.all( filters.map(filter => { - return indexPatternsService.get(filter.meta.index!); + return getIndexPatterns().get(filter.meta.index!); }) ); const filterSelectionPromise: Promise = new Promise(resolve => { - const overlay = overlays.openModal( + const overlay = getOverlays().openModal( toMountPoint( applyFiltersPopover( filters, diff --git a/src/plugins/data/public/actions/index.ts b/src/plugins/data/public/actions/index.ts new file mode 100644 index 00000000000000..5d469606944a18 --- /dev/null +++ b/src/plugins/data/public/actions/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 { GLOBAL_APPLY_FILTER_ACTION, createFilterAction } from './apply_filter_action'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index eca6258099141e..e54278698a05a8 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -18,14 +18,10 @@ */ import { PluginInitializerContext } from '../../../core/public'; -import { DataPublicPlugin } from './plugin'; - export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } -export { DataPublicPlugin as Plugin }; - export * from '../common'; export * from './autocomplete_provider'; @@ -39,3 +35,7 @@ export * from './search'; export * from './query'; export * from './ui'; + +// Export plugin after all other imports +import { DataPublicPlugin } from './plugin'; +export { DataPublicPlugin as Plugin }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 6ec0413968a553..2a37be7f3f46a1 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -19,7 +19,12 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { Storage } from '../../kibana_utils/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from './types'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, + DataSetupDependencies, + DataStartDependencies, +} from './types'; import { AutocompleteProviderRegister } from './autocomplete_provider'; import { getSuggestionsProvider } from './suggestions_provider'; import { SearchService } from './search/search_service'; @@ -27,7 +32,9 @@ import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; import { IndexPatterns } from './index_patterns'; -import { setNotifications, setFieldFormats } from './services'; +import { setNotifications, setFieldFormats, setOverlays, setIndexPatterns } from './services'; +import { createFilterAction, GLOBAL_APPLY_FILTER_ACTION } from './actions'; +import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; export class DataPublicPlugin implements Plugin { private readonly autocomplete = new AutocompleteProviderRegister(); @@ -41,27 +48,36 @@ export class DataPublicPlugin implements Plugin( 'Notifications' @@ -28,3 +30,9 @@ export const [getNotifications, setNotifications] = createGetterSetter( 'FieldFormats' ); + +export const [getOverlays, setOverlays] = createGetterSetter('Overlays'); + +export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( + 'IndexPatterns' +); diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index dd70c5646f708e..202a509ee58c95 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -19,6 +19,7 @@ import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { IUiActionsSetup, IUiActionsStart } from 'src/plugins/ui_actions/public'; import { AutocompletePublicPluginSetup, AutocompletePublicPluginStart } from '.'; import { FieldFormatsSetup, FieldFormatsStart } from './field_formats_provider'; import { ISearchSetup, ISearchStart } from './search'; @@ -27,6 +28,14 @@ import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; import { IndexPatternsContract } from './index_patterns'; +export interface DataSetupDependencies { + uiActions: IUiActionsSetup; +} + +export interface DataStartDependencies { + uiActions: IUiActionsStart; +} + export interface DataPublicPluginSetup { autocomplete: AutocompletePublicPluginSetup; search: ISearchSetup; diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 9a4bccc21db3e5..8bfccd49bdff34 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -20,7 +20,6 @@ export { SuggestionsComponent } from './typeahead/suggestions_component'; export { IndexPatternSelect } from './index_pattern_select'; export { FilterBar } from './filter_bar'; -export { applyFiltersPopover } from './apply_filters'; export { QueryStringInput } from './query_string_input/query_string_input'; // temp export - will be removed as final components are migrated to NP From 5a14a7a3851c883931bbaaa90a9c14cec6cd1bd5 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 9 Dec 2019 17:29:08 +0100 Subject: [PATCH 07/36] Graph: Stabilize functional test (#52417) --- x-pack/test/functional/apps/graph/graph.ts | 4 ++-- x-pack/test/functional/page_objects/graph_page.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index 58ee1668df7012..2bbc39969370bf 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,8 +13,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - // FLAKY: https://github.com/elastic/kibana/issues/45321 - describe.skip('graph', function() { + describe('graph', function() { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data'); @@ -114,6 +113,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const circlesText = nodes.map(({ label }) => label); expect(circlesText.length).to.equal(expectedNodes.length); circlesText.forEach(circleText => { + log.debug(`Looking for ${circleText}`); expect(expectedNodes.includes(circleText)).to.be(true); }); }); diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 780a541db08908..0d3e2c10579f54 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -235,6 +235,11 @@ export function GraphPageProvider({ getService, getPageObjects }: FtrProviderCon await this.goToListingPage(); await this.searchForWorkspaceWithName(name); await find.clickByLinkText(name); + // wait for nodes to show up + if (!(await find.existsByCssSelector('.gphNode', 10000))) { + throw new Error('nodes did not show up'); + } + // let force simulation settle down before continuing await PageObjects.common.sleep(5000); } From ac0f44e6a50235b8673d1541fec21cfd8ffc548c Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 9 Dec 2019 10:51:17 -0600 Subject: [PATCH 08/36] Upgrade EUI to v17.0.0 (#52342) * eui to 17.0.0 * Fix sass imports and mixin usages * Fix kbn ui-framework * snapshot updates * switch text * switch functional update * test subject --- package.json | 2 +- packages/kbn-ui-framework/dist/kui_dark.css | 17 ++- packages/kbn-ui-framework/dist/kui_light.css | 33 +++-- packages/kbn-ui-framework/src/kui_dark.scss | 2 - packages/kbn-ui-framework/src/kui_light.scss | 2 - .../kibana/public/discover/_discover.scss | 5 +- .../core_plugins/kibana/public/index.scss | 2 - .../vis_type_timeseries/public/_mixins.scss | 3 - .../components/_annotations_editor.scss | 4 +- .../public/components/_series_editor.scss | 4 +- src/legacy/server/sass/build.test.js | 122 ++++++++++++---- .../ui/public/styles/_legacy/_mixins.scss | 2 - .../styles/_legacy/components/_ui_select.scss | 138 +++++++++--------- .../vis/components/tooltip/_tooltip.scss | 3 - .../public/vis/editors/default/_sidebar.scss | 7 +- .../ui/filter_bar/_global_filter_item.scss | 3 - .../_saved_query_management_component.scss | 2 - .../public/views/requests/_requests.scss | 2 - .../plugins/kbn_tp_run_pipeline/package.json | 2 +- .../kbn_tp_custom_visualizations/package.json | 2 +- .../kbn_tp_embeddable_explorer/package.json | 2 +- .../kbn_tp_sample_panel_action/package.json | 2 +- .../extended_template.examples.storyshot | 2 + .../custom_element_modal.examples.storyshot | 12 ++ .../expression_input.examples.storyshot | 1 + .../tooltip_annotation.scss | 3 - .../extended_template.examples.storyshot | 21 +++ .../extended_template.examples.storyshot | 4 + .../autoplay_settings.examples.storyshot | 18 ++- .../toolbar_settings.examples.storyshot | 15 +- .../__snapshots__/settings.test.tsx.snap | 13 +- .../indexpattern_plugin/_datapanel.scss | 3 - .../chart_tooltip/_chart_tooltip.scss | 4 - .../create_analytics_form.test.tsx | 2 +- .../fields_stats/_field_stats_card.scss | 13 +- .../field_data_card/_field_data_card.scss | 7 +- x-pack/legacy/plugins/ml/public/index.scss | 5 - .../remote_cluster_form.test.js.snap | 14 +- .../np_ready/application/styles/_index.scss | 2 - .../application/styles/containers/_main.scss | 7 +- .../note_card_body.test.tsx.snap | 5 +- .../__snapshots__/index.test.tsx.snap | 5 +- .../components/_confirm_delete_modal.scss | 4 +- .../views/space_selector/_space_selector.scss | 3 - x-pack/package.json | 2 +- .../page_objects/upgrade_assistant.js | 2 +- yarn.lock | 8 +- 47 files changed, 313 insertions(+), 223 deletions(-) diff --git a/package.json b/package.json index 847f09b4ab4cfe..cf36d4ce884acb 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", diff --git a/packages/kbn-ui-framework/dist/kui_dark.css b/packages/kbn-ui-framework/dist/kui_dark.css index dcbd65fbca520b..aa16bcdb34d368 100644 --- a/packages/kbn-ui-framework/dist/kui_dark.css +++ b/packages/kbn-ui-framework/dist/kui_dark.css @@ -1,10 +1,24 @@ +/* 1 */ +/* 1 */ +/** + * 1. Extend beta badges to at least 40% of the container's width + * 2. Fix for IE to ensure badges are visible outside of a -

Cycle Slides -

+

-

Cycle Slides -

+

-

Cycle Slides -

+

-

Hide Toolbar -

+
-

Hide Toolbar -

+
-

Hide Toolbar -

+
can navigate Autoplay Settings 2`] = ` -

Cycle Slides -

+

can navigate Autoplay Settings 2`] = ` >
can navigate Autoplay Settings 2`] = ` >
can navigate Toolbar Settings, closes when activated 2`] = >
can navigate Toolbar Settings, closes when activated 2`] = -

Hide Toolbar -

+
can navigate Toolbar Settings, closes when activated 2`] =
`; -exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings

Hide Toolbar

Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss index a496cccc42a581..ed39beeb7d088b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/form_control_layout/mixins'; - .lnsInnerIndexPatternDataPanel { width: 100%; height: 100%; @@ -54,4 +52,3 @@ @include euiFormControlLayoutPadding(1, 'right'); @include euiFormControlLayoutPadding(1, 'left'); } - diff --git a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss index ea5c245d9e39fe..25bf3597c34665 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss +++ b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss @@ -1,6 +1,3 @@ -@import '@elastic/eui/src/components/tool_tip/variables'; -@import '@elastic/eui/src/components/tool_tip/mixins'; - .mlChartTooltip { @include euiToolTipStyle('s'); @include euiFontSizeXS; @@ -50,5 +47,4 @@ &--hidden { opacity: 0; } - } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx index b85591d13998ac..49381e3b1c0312 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx @@ -56,6 +56,6 @@ describe('Data Frame Analytics: ', () => { expect(options.at(2).props().value).toBe('regression'); const row2 = euiFormRows.at(1); - expect(row2.find('p').text()).toBe('Enable advanced editor'); + expect(row2.find('EuiSwitch').text()).toBe('Enable advanced editor'); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss index 6137a21d0bd8d8..39a87ece68ac94 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss @@ -41,12 +41,13 @@ background-color: #920000; } - .type-other, .unknown { + .type-other, + .unknown { background-color: #bfa180; } // Use euiPanel styling - @include euiPanel($selector: 'card-contents'); + @include euiPanel($selector: '.card-contents'); .card-contents { height: 378px; @@ -68,12 +69,16 @@ padding-bottom: 0px; } - .stat.min, .stat.max, .stat.median { + .stat.min, + .stat.max, + .stat.median { width: 30%; display: inline-block; } - .stat.min.value, .stat.max.value, .stat.median.value { + .stat.min.value, + .stat.max.value, + .stat.median.value { font-size: $euiFontSizeS; @include euiTextTruncate; } diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss index ca7d8e3f31c582..b4fd521f21bec5 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss @@ -2,7 +2,6 @@ height: 420px; width: 360px; - // Note the names of these styles need to match the type of the field they are displaying. .boolean { background-color: $euiColorVis5; @@ -36,13 +35,13 @@ background-color: $euiColorVis9; } - .type-other, .unknown { + .type-other, + .unknown { background-color: $euiColorVis6; } - // Use euiPanel styling - @include euiPanel($selector: 'mlFieldDataCard__content'); + @include euiPanel($selector: '.mlFieldDataCard__content'); .mlFieldDataCard__content { @include euiFontSizeS; diff --git a/x-pack/legacy/plugins/ml/public/index.scss b/x-pack/legacy/plugins/ml/public/index.scss index c3216773c1a324..ac3f3fef97c702 100644 --- a/x-pack/legacy/plugins/ml/public/index.scss +++ b/x-pack/legacy/plugins/ml/public/index.scss @@ -1,10 +1,6 @@ // Should import both the EUI constants and any Kibana ones that are considered global @import 'src/legacy/ui/public/styles/styling_constants'; -// ML needs EUI card styling till it fully adopts React components -@import '@elastic/eui/src/components/panel/variables'; -@import '@elastic/eui/src/components/panel/mixins'; - // ML has it's own variables for coloring @import 'application/variables'; @@ -16,7 +12,6 @@ // Protect the rest of Kibana from ML generic namespacing // SASSTODO: Prefix ml selectors instead #ml-app { - // App level @import 'application/app'; diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index bf309c65556a81..6ebc43bb927377 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -40,6 +40,7 @@ Array [
-

Skip if unavailable -

+
@@ -367,6 +370,7 @@ Array [
-

Skip if unavailable -

+
, diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss index d36a587b9257f5..5c35e9a23b8a1a 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss @@ -1,5 +1,4 @@ @import '@elastic/eui/src/components/header/variables'; -@import '@elastic/eui/src/components/panel/mixins'; @import 'mixins'; @@ -16,7 +15,6 @@ } } - .prfDevTool__page { height: 100%; display: flex; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss index 09bcddef02cc34..6e2ef4a1293978 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss @@ -1,4 +1,3 @@ - @include euiBreakpoint('xs', 's') { .prfDevTool__shardDetailsWrapper { flex-direction: column; @@ -34,7 +33,8 @@ font-size: $euiSize; color: $euiColorMediumShade; } - h1, p { + h1, + p { cursor: default; user-select: none; } @@ -44,7 +44,7 @@ } } -@include euiPanel('prfDevTool__main'); +@include euiPanel('.prfDevTool__main'); @include euiBreakpoint('xs', 's') { .prfDevTool__container { @@ -59,4 +59,3 @@ margin: $euiSize 0; } } - diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap index 526bb52c61afaf..d149a33d3b20d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap @@ -100,7 +100,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "success": "#7de2d1", "warning": "#ffce7a", }, - "euiCardGraphicHeight": "40px", + "euiCardBottomNodeHeight": "40px", "euiCardSelectButtonBackgrounds": Object { "danger": "#4d1f1f", "ghost": "#98a2b3", @@ -116,7 +116,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "text": "#7de2d1", }, "euiCardSpacing": "16px", - "euiCardTitleSize": "18px", "euiCheckBoxSize": "16px", "euiCodeBlockAdditionBackgroundColor": "#144212", "euiCodeBlockAdditionColor": "#e6e1dc", @@ -290,7 +289,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiNavDrawerWidthCollapsed": "48px", "euiNavDrawerWidthExpanded": "240px", "euiPageBackgroundColor": "#1a1b20", - "euiPanelBorderColor": "#000000", "euiPanelPaddingModifiers": Object { "paddingLarge": "24px", "paddingMedium": "16px", @@ -334,6 +332,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiScrollBar": "16px", "euiScrollBarCorner": "6px", "euiSelectableListItemBorder": "1px solid #202128", + "euiSelectableListItemPadding": "4px 12px", "euiShadowColor": "#000000", "euiShadowColorLarge": "#000000", "euiSize": "16px", diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap index ddeb701c3ee31c..f637c155538236 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -100,7 +100,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "success": "#7de2d1", "warning": "#ffce7a", }, - "euiCardGraphicHeight": "40px", + "euiCardBottomNodeHeight": "40px", "euiCardSelectButtonBackgrounds": Object { "danger": "#4d1f1f", "ghost": "#98a2b3", @@ -116,7 +116,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "text": "#7de2d1", }, "euiCardSpacing": "16px", - "euiCardTitleSize": "18px", "euiCheckBoxSize": "16px", "euiCodeBlockAdditionBackgroundColor": "#144212", "euiCodeBlockAdditionColor": "#e6e1dc", @@ -290,7 +289,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiNavDrawerWidthCollapsed": "48px", "euiNavDrawerWidthExpanded": "240px", "euiPageBackgroundColor": "#1a1b20", - "euiPanelBorderColor": "#000000", "euiPanelPaddingModifiers": Object { "paddingLarge": "24px", "paddingMedium": "16px", @@ -334,6 +332,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiScrollBar": "16px", "euiScrollBarCorner": "6px", "euiSelectableListItemBorder": "1px solid #202128", + "euiSelectableListItemPadding": "4px 12px", "euiShadowColor": "#000000", "euiShadowColorLarge": "#000000", "euiSize": "16px", diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss b/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss index 04bc51067d9dfc..887495a231485b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .spcConfirmDeleteModal { - max-width: $euiFormMaxWidth + ($euiSizeL*2); + max-width: $euiFormMaxWidth + ($euiSizeL * 2); } diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss b/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss index 3fd2f91b25f4a4..cfa2fd650e83bf 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss +++ b/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .spcSpaceSelector { @include kibanaFullScreenGraphics; } @@ -27,7 +25,6 @@ @include euiBottomShadowMedium; } - .spcSpaceSelector__searchHolder { width: $euiFormMaxWidth; // make sure it's as wide as our default form element width max-width: 100%; diff --git a/x-pack/package.json b/x-pack/package.json index 075a7867b4720b..74e6341acc675e 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -176,7 +176,7 @@ "@babel/runtime": "^7.5.5", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", diff --git a/x-pack/test/functional/page_objects/upgrade_assistant.js b/x-pack/test/functional/page_objects/upgrade_assistant.js index c6977b21508403..fc6faed170c9f4 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant.js +++ b/x-pack/test/functional/page_objects/upgrade_assistant.js @@ -45,7 +45,7 @@ export function UpgradeAssistantProvider({ getService, getPageObjects }) { async expectDeprecationLoggingLabel(labelText) { return await retry.try(async () => { log.debug('expectDeprecationLoggingLabel()'); - const label = await find.byCssSelector('[data-test-subj="upgradeAssistantDeprecationToggle"] ~ p'); + const label = await find.byCssSelector('[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span'); const value = await label.getVisibleText(); expect(value).to.equal(labelText); }); diff --git a/yarn.lock b/yarn.lock index 57249563f4566a..6df7ab3d4243dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1226,10 +1226,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@16.1.0": - version "16.1.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-16.1.0.tgz#b8311a4dd3146da2fcd8cec6ae1ed46e26f90746" - integrity sha512-WbfIc2RGojrUeYyEIjiQ0Cy4xbk6YJ6eQ+ymFHSDGqwkKy5IidSBZIDNLM13LzAhxBFTAMmfVsk4lB2qiFlolQ== +"@elastic/eui@17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-17.0.0.tgz#2b53500b9f4849fdf58f88ea89e10a0b58836acc" + integrity sha512-bWTVKbChPGh7AHLxW1o9yp7O3hL9lTVj69DVaaLBOz9K0JnDrWZ5VDvWL9AAguFWq9OEMY9oWjyBhQZr1ZeZbA== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" From 134e70e16df2aafb278c089ad4ce41ca79423bcd Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Mon, 9 Dec 2019 12:03:41 -0500 Subject: [PATCH 09/36] Fix timing issue with synchronizing the Kibana privileges to ES (#52214) --- x-pack/legacy/plugins/security/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index fc83dc64aafab5..1d798a4a2bc400 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -110,7 +110,8 @@ export const security = (kibana) => new kibana.Plugin({ } watchStatusAndLicenseToInitialize(server.plugins.xpack_main, this, async () => { - if (securityPlugin.__legacyCompat.license.getFeatures().allowRbac) { + const xpackInfo = server.plugins.xpack_main.info; + if (xpackInfo.isAvailable() && xpackInfo.feature('security').isEnabled()) { await securityPlugin.__legacyCompat.registerPrivilegesWithCluster(); } }); From b3cb1ca74894748f62cc3d7d6dd7dd3c29cbf220 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 9 Dec 2019 12:44:59 -0500 Subject: [PATCH 10/36] adding in updated links for feedback and ask (#52516) --- src/core/public/chrome/constants.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/public/chrome/constants.ts b/src/core/public/chrome/constants.ts index 8fcf3e6d622318..9c76e2b1ee84e2 100644 --- a/src/core/public/chrome/constants.ts +++ b/src/core/public/chrome/constants.ts @@ -17,6 +17,8 @@ * under the License. */ -export const KIBANA_FEEDBACK_LINK = 'https://www.elastic.co/products/kibana/feedback'; -export const KIBANA_ASK_ELASTIC_LINK = 'https://www.elastic.co/products/kibana/ask-elastic'; +export const KIBANA_FEEDBACK_LINK = + 'https://www.elastic.co/products/kibana/feedback?blade=kibanafeedback'; +export const KIBANA_ASK_ELASTIC_LINK = + 'https://www.elastic.co/products/kibana/ask-elastic?blade=kibanaaskelastic'; export const GITHUB_CREATE_ISSUE_LINK = 'https://github.com/elastic/kibana/issues/new/choose'; From 77f4f8c2501e54d4db96b61757218de04e9151e3 Mon Sep 17 00:00:00 2001 From: Kukhyeon Heo Date: Tue, 10 Dec 2019 02:56:51 +0900 Subject: [PATCH 11/36] ui/management -> new platform (#45747) Created management plugin under core_plugins. Filled the plugin with 2 services: IndexPatternManagementService and SavedObjectsManagementService. Removed related codes in ui/management and changed the paths. --- .i18nrc.json | 1 + .../step_time_field/step_time_field.js | 6 +- .../create_index_pattern_wizard/index.js | 14 +- .../edit_index_pattern/edit_index_pattern.js | 96 ++++++++----- .../sections/index_patterns/index.js | 112 +++++++-------- .../__jest__/objects_table.test.js | 6 + .../components/flyout/__jest__/flyout.test.js | 7 +- .../components/table/__jest__/table.test.js | 6 + .../objects_table/components/table/table.js | 4 +- src/legacy/core_plugins/management/index.ts | 37 +++++ .../core_plugins/management/package.json | 5 + .../management/public}/index.ts | 19 ++- .../core_plugins/management/public/legacy.ts | 45 ++++++ .../management/public/np_ready/index.ts | 47 +++++++ .../management/public/np_ready/mocks.ts | 66 +++++++++ .../management/public/np_ready/plugin.ts | 67 +++++++++ .../public/np_ready/services/index.ts} | 4 +- .../creation/config.ts} | 68 ++++++--- .../creation/index.ts} | 9 +- .../creation/manager.ts | 61 +++++++++ .../index_pattern_management/index.ts} | 7 +- .../index_pattern_management_service.ts | 53 +++++++ .../index_pattern_management/list/config.ts | 51 +++++++ .../index_pattern_management/list/index.ts} | 6 +- .../index_pattern_management/list/manager.ts | 57 ++++++++ .../saved_objects_management/index.ts | 22 +++ .../saved_objects_management_action.ts | 2 +- ...objects_management_action_registry.test.ts | 0 ...aved_objects_management_action_registry.ts | 0 .../saved_objects_management_service.ts} | 25 ++-- .../index_pattern_creation/index.js | 23 ---- .../index_pattern_creation.js | 54 -------- .../management/index_pattern_list/index.js | 23 ---- .../index_pattern_list/index_pattern_list.js | 52 ------- src/legacy/ui/public/management/section.js | 15 +- .../ui/public/management/section.test.js | 36 +++-- .../ui/public/management/sections_register.js | 8 +- .../public/index_pattern_creation/register.js | 4 +- .../rollup_index_pattern_creation_config.js | 129 ++++++++++-------- .../public/index_pattern_list/register.js | 4 +- .../rollup_index_pattern_list_config.js | 66 +++++---- .../plugins/rollup/public/services/api.js | 9 +- .../copy_saved_objects_to_space_action.tsx | 6 +- .../summarize_copy_result.test.ts | 2 +- .../summarize_copy_result.ts | 6 +- .../spaces/public/lib/spaces_manager.ts | 2 +- .../copy_to_space_flyout.test.tsx | 6 + .../copy_to_space_flyout.tsx | 8 +- .../copy_to_space_flyout_footer.tsx | 2 +- .../processing_copy_to_space.tsx | 6 +- .../space_result.tsx | 2 +- .../space_result_details.tsx | 2 +- .../spaces/public/views/management/index.tsx | 6 +- .../translations/translations/ja-JP.json | 6 +- .../translations/translations/zh-CN.json | 6 +- x-pack/test/functional/config.js | 4 +- 56 files changed, 930 insertions(+), 460 deletions(-) create mode 100644 src/legacy/core_plugins/management/index.ts create mode 100644 src/legacy/core_plugins/management/package.json rename src/legacy/{ui/public/management/saved_objects_management => core_plugins/management/public}/index.ts (72%) create mode 100644 src/legacy/core_plugins/management/public/legacy.ts create mode 100644 src/legacy/core_plugins/management/public/np_ready/index.ts create mode 100644 src/legacy/core_plugins/management/public/np_ready/mocks.ts create mode 100644 src/legacy/core_plugins/management/public/np_ready/plugin.ts rename src/legacy/{ui/public/management/index_pattern_creation/index_pattern_types.js => core_plugins/management/public/np_ready/services/index.ts} (87%) rename src/legacy/{ui/public/management/index_pattern_creation/index_pattern_creation_config.js => core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts} (55%) rename src/legacy/{ui/public/management/index_pattern_list/index_pattern_list_config_registry.js => core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts} (81%) create mode 100644 src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts rename src/legacy/{ui/public/management/index_pattern_creation/register.js => core_plugins/management/public/np_ready/services/index_pattern_management/index.ts} (80%) create mode 100644 src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts create mode 100644 src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts rename src/legacy/{ui/public/management/index_pattern_list/register.js => core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts} (77%) create mode 100644 src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts create mode 100644 src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts rename src/legacy/{ui/public/management => core_plugins/management/public/np_ready/services}/saved_objects_management/saved_objects_management_action.ts (96%) rename src/legacy/{ui/public/management => core_plugins/management/public/np_ready/services}/saved_objects_management/saved_objects_management_action_registry.test.ts (100%) rename src/legacy/{ui/public/management => core_plugins/management/public/np_ready/services}/saved_objects_management/saved_objects_management_action_registry.ts (100%) rename src/legacy/{ui/public/management/index_pattern_list/index_pattern_list_config.js => core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts} (68%) delete mode 100644 src/legacy/ui/public/management/index_pattern_creation/index.js delete mode 100644 src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js delete mode 100644 src/legacy/ui/public/management/index_pattern_list/index.js delete mode 100644 src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js diff --git a/.i18nrc.json b/.i18nrc.json index fac9b9ce531842..a1c49ae03f3593 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -17,6 +17,7 @@ "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", + "management": "src/legacy/core_plugins/management", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js index 5cb93a36cdd184..1e894664c2bf18 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.js @@ -50,8 +50,6 @@ export class StepTimeField extends Component { constructor(props) { super(props); - const { getIndexPatternType, getIndexPatternName } = props.indexPatternCreationType; - this.state = { error: '', timeFields: [], @@ -61,8 +59,8 @@ export class StepTimeField extends Component { isFetchingTimeFields: false, isCreating: false, indexPatternId: '', - indexPatternType: getIndexPatternType(), - indexPatternName: getIndexPatternName(), + indexPatternType: props.indexPatternCreationType.getIndexPatternType(), + indexPatternName: props.indexPatternCreationType.getIndexPatternName(), }; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js index b500f5c79e98bd..833ca8467292e3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js @@ -21,7 +21,7 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects'; import uiRoutes from 'ui/routes'; import angularTemplate from './angular_template.html'; import 'ui/index_patterns'; -import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../../management/public/legacy'; import { getCreateBreadcrumbs } from '../breadcrumbs'; import { renderCreateIndexPatternWizard, destroyCreateIndexPatternWizard } from './render'; @@ -35,8 +35,9 @@ uiRoutes.when('/management/kibana/index_pattern', { const Private = $injector.get('Private'); $scope.$$postDigest(() => { const $routeParams = $injector.get('$routeParams'); - const indexPatternCreationProvider = Private(IndexPatternCreationFactory)($routeParams.type); - const indexPatternCreationType = indexPatternCreationProvider.getType(); + const indexPatternCreationType = managementSetup.indexPattern.creation.getType( + $routeParams.type + ); const services = { config: $injector.get('config'), es: $injector.get('es'), @@ -52,12 +53,9 @@ uiRoutes.when('/management/kibana/index_pattern', { const initialQuery = $routeParams.id ? decodeURIComponent($routeParams.id) : undefined; - renderCreateIndexPatternWizard( - initialQuery, - services - ); + renderCreateIndexPatternWizard(initialQuery, services); }); $scope.$on('$destroy', destroyCreateIndexPatternWizard); - } + }, }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index 6ae84b9c641c24..150fae6e87ddec 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -28,7 +28,7 @@ import uiRoutes from 'ui/routes'; import { uiModules } from 'ui/modules'; import template from './edit_index_pattern.html'; import { fieldWildcardMatcher } from 'ui/field_wildcard'; -import { IndexPatternListFactory } from 'ui/management/index_pattern_list'; +import { setup as managementSetup } from '../../../../../../management/public/legacy'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { SourceFiltersTable } from './source_filters_table'; @@ -58,13 +58,17 @@ function updateSourceFiltersTable($scope, $state) { filterFilter={$scope.fieldFilter} fieldWildcardMatcher={$scope.fieldWildcardMatcher} onAddOrRemoveFilter={() => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, $scope.indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + $scope.indexPatternListProvider + ); $scope.refreshFilters(); $scope.$apply(); }} /> , - node, + node ); }); } else { @@ -77,7 +81,6 @@ function destroySourceFiltersTable() { node && unmountComponentAtNode(node); } - function updateScriptedFieldsTable($scope, $state) { if ($state.tab === 'scriptedFields') { $scope.$$postDigest(() => { @@ -100,13 +103,17 @@ function updateScriptedFieldsTable($scope, $state) { getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route), }} onRemoveField={() => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, $scope.indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + $scope.indexPatternListProvider + ); $scope.refreshFilters(); $scope.$apply(); }} /> , - node, + node ); }); } else { @@ -144,7 +151,7 @@ function updateIndexedFieldsTable($scope, $state) { }} /> , - node, + node ); }); } else { @@ -157,34 +164,36 @@ function destroyIndexedFieldsTable() { node && unmountComponentAtNode(node); } -uiRoutes - .when('/management/kibana/index_patterns/:indexPatternId', { - template, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) { - return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)) - .catch(redirectWhenMissing('/management/kibana/index_patterns')); - } +uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', { + template, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) { + return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId)).catch( + redirectWhenMissing('/management/kibana/index_patterns') + ); }, - }); + }, +}); -uiModules.get('apps/management') +uiModules + .get('apps/management') .controller('managementIndexPatternsEdit', function ( $scope, $location, $route, Promise, config, indexPatterns, Private, AppState, confirmModal) { const $state = $scope.state = new AppState(); - const indexPatternListProvider = Private(IndexPatternListFactory)(); $scope.fieldWildcardMatcher = (...args) => fieldWildcardMatcher(...args, config.get('metaFields')); $scope.editSectionsProvider = Private(IndicesEditSectionsProvider); $scope.kbnUrl = Private(KbnUrlProvider); $scope.indexPattern = $route.current.locals.indexPattern; - $scope.indexPatternListProvider = indexPatternListProvider; - $scope.indexPattern.tags = indexPatternListProvider.getIndexPatternTags( + $scope.indexPatternListProvider = managementSetup.indexPattern.list; + $scope.indexPattern.tags = managementSetup.indexPattern.list.getIndexPatternTags( $scope.indexPattern, $scope.indexPattern.id === config.get('defaultIndex') ); - $scope.getFieldInfo = indexPatternListProvider.getFieldInfo; + $scope.getFieldInfo = managementSetup.indexPattern.list.getFieldInfo.bind( + managementSetup.indexPattern.list + ); docTitle.change($scope.indexPattern.title); const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { @@ -192,7 +201,11 @@ uiModules.get('apps/management') }); $scope.$watch('indexPattern.fields', function () { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + managementSetup.indexPattern.list + ); $scope.refreshFilters(); $scope.fields = $scope.indexPattern.getNonScriptedFields(); updateIndexedFieldsTable($scope, $state); @@ -231,26 +244,26 @@ uiModules.get('apps/management') }); $scope.$watchCollection('indexPattern.fields', function () { - $scope.conflictFields = $scope.indexPattern.fields - .filter(field => field.type === 'conflict'); + $scope.conflictFields = $scope.indexPattern.fields.filter(field => field.type === 'conflict'); }); $scope.refreshFields = function () { const confirmMessage = i18n.translate('kbn.management.editIndexPattern.refreshLabel', { - defaultMessage: 'This action resets the popularity counter of each field.' + defaultMessage: 'This action resets the popularity counter of each field.', }); const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { defaultMessage: 'Refresh' }), + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', { + defaultMessage: 'Refresh', + }), onConfirm: async () => { await $scope.indexPattern.init(true); $scope.fields = $scope.indexPattern.getNonScriptedFields(); }, - title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { defaultMessage: 'Refresh field list?' }) + title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', { + defaultMessage: 'Refresh field list?', + }), }; - confirmModal( - confirmMessage, - confirmModalOptions - ); + confirmModal(confirmMessage, confirmModalOptions); }; $scope.removePattern = function () { @@ -271,9 +284,13 @@ uiModules.get('apps/management') } const confirmModalOptions = { - confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { defaultMessage: 'Delete' }), + confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', { + defaultMessage: 'Delete', + }), onConfirm: doRemove, - title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { defaultMessage: 'Delete index pattern?' }) + title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', { + defaultMessage: 'Delete index pattern?', + }), }; confirmModal('', confirmModalOptions); }; @@ -285,7 +302,8 @@ uiModules.get('apps/management') $scope.setIndexPatternsTimeField = function (field) { if (field.type !== 'date') { const errorMessage = i18n.translate('kbn.management.editIndexPattern.notDateErrorMessage', { - defaultMessage: 'That field is a {fieldType} not a date.', values: { fieldType: field.type } + defaultMessage: 'That field is a {fieldType} not a date.', + values: { fieldType: field.type }, }); toastNotifications.addDanger(errorMessage); return; @@ -295,12 +313,16 @@ uiModules.get('apps/management') }; $scope.$watch('fieldFilter', () => { - $scope.editSections = $scope.editSectionsProvider($scope.indexPattern, $scope.fieldFilter, indexPatternListProvider); + $scope.editSections = $scope.editSectionsProvider( + $scope.indexPattern, + $scope.fieldFilter, + managementSetup.indexPattern.list + ); if ($scope.fieldFilter === undefined) { return; } - switch($state.tab) { + switch ($state.tab) { case 'indexedFields': updateIndexedFieldsTable($scope, $state); case 'scriptedFields': diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index 272544fa036cce..5935afec1dd707 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,8 +18,7 @@ */ import { management } from 'ui/management'; -import { IndexPatternListFactory } from 'ui/management/index_pattern_list'; -import { IndexPatternCreationFactory } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../management/public/legacy'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -28,7 +27,10 @@ import indexTemplate from './index.html'; import indexPatternListTemplate from './list.html'; import { IndexPatternTable } from './index_pattern_table'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { + FeatureCatalogueRegistryProvider, + FeatureCatalogueCategory, +} from 'ui/registry/feature_catalogue'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { UICapabilitiesProvider } from 'ui/capabilities/react'; @@ -39,11 +41,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; const INDEX_PATTERN_LIST_DOM_ELEMENT_ID = 'indexPatternListReact'; -export function updateIndexPatternList( - indexPatterns, - kbnUrl, - indexPatternCreationOptions, -) { +export function updateIndexPatternList(indexPatterns, kbnUrl, indexPatternCreationOptions) { const node = document.getElementById(INDEX_PATTERN_LIST_DOM_ELEMENT_ID); if (!node) { return; @@ -59,7 +57,7 @@ export function updateIndexPatternList( /> , - node, + node ); } @@ -72,55 +70,56 @@ const indexPatternsResolutions = { indexPatterns: function (Private) { const savedObjectsClient = Private(SavedObjectsClientProvider); - return savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'type'], - perPage: 10000 - }).then(response => response.savedObjects); - } + return savedObjectsClient + .find({ + type: 'index-pattern', + fields: ['title', 'type'], + perPage: 10000, + }) + .then(response => response.savedObjects); + }, }; // add a dependency to all of the subsection routes -uiRoutes - .defaults(/management\/kibana\/(index_patterns|index_pattern)/, { - resolve: indexPatternsResolutions, - requireUICapability: 'management.kibana.index_patterns', - badge: uiCapabilities => { - if (uiCapabilities.indexPatterns.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.management.indexPatterns.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.management.indexPatterns.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save index patterns', - }), - iconType: 'glasses' - }; +uiRoutes.defaults(/management\/kibana\/(index_patterns|index_pattern)/, { + resolve: indexPatternsResolutions, + requireUICapability: 'management.kibana.index_patterns', + badge: uiCapabilities => { + if (uiCapabilities.indexPatterns.save) { + return undefined; } - }); -uiRoutes - .when('/management/kibana/index_patterns', { - template: indexPatternListTemplate, - k7Breadcrumbs: getListBreadcrumbs - }); + return { + text: i18n.translate('kbn.management.indexPatterns.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.management.indexPatterns.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save index patterns', + }), + iconType: 'glasses', + }; + }, +}); + +uiRoutes.when('/management/kibana/index_patterns', { + template: indexPatternListTemplate, + k7Breadcrumbs: getListBreadcrumbs, +}); // wrapper directive, which sets some global stuff up like the left nav -uiModules.get('apps/management') - .directive('kbnManagementIndexPatterns', function ($route, config, kbnUrl, Private) { +uiModules + .get('apps/management') + .directive('kbnManagementIndexPatterns', function ($route, config, kbnUrl) { return { restrict: 'E', transclude: true, template: indexTemplate, link: async function ($scope) { - const indexPatternListProvider = Private(IndexPatternListFactory)(); - const indexPatternCreationProvider = Private(IndexPatternCreationFactory)(); - const indexPatternCreationOptions = await indexPatternCreationProvider.getIndexPatternCreationOptions((url) => { - $scope.$evalAsync(() => kbnUrl.change(url)); - }); + const indexPatternCreationOptions = await managementSetup.indexPattern.creation.getIndexPatternCreationOptions( + url => { + $scope.$evalAsync(() => kbnUrl.change(url)); + } + ); const renderList = () => { $scope.indexPatternList = @@ -129,7 +128,7 @@ uiModules.get('apps/management') const id = pattern.id; const title = pattern.get('title'); const isDefault = $scope.defaultIndex === id; - const tags = indexPatternListProvider.getIndexPatternTags( + const tags = managementSetup.indexPattern.list.getIndexPatternTags( pattern, isDefault ); @@ -165,25 +164,30 @@ uiModules.get('apps/management') $scope.$watch('defaultIndex', () => renderList()); config.bindToScope($scope, 'defaultIndex'); $scope.$apply(); - } + }, }; }); management.getSection('kibana').register('index_patterns', { - display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { defaultMessage: 'Index Patterns' }), + display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { + defaultMessage: 'Index Patterns', + }), order: 0, - url: '#/management/kibana/index_patterns/' + url: '#/management/kibana/index_patterns/', }); FeatureCatalogueRegistryProvider.register(() => { return { id: 'index_patterns', - title: i18n.translate('kbn.management.indexPatternHeader', { defaultMessage: 'Index Patterns' }), - description: i18n.translate('kbn.management.indexPatternLabel', - { defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.' }), + title: i18n.translate('kbn.management.indexPatternHeader', { + defaultMessage: 'Index Patterns', + }), + description: i18n.translate('kbn.management.indexPatternLabel', { + defaultMessage: 'Manage the index patterns that help retrieve your data from Elasticsearch.', + }), icon: 'indexPatternApp', path: '/app/kibana#/management/kibana/index_patterns', showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN + category: FeatureCatalogueCategory.ADMIN, }; }); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index 5956b6c306b0e5..1c3666ac0980c6 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -19,6 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../../../../../management/public/np_ready/mocks'; import { Query } from '@elastic/eui'; import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; @@ -29,6 +30,11 @@ import { extractExportDetails } from '../../../lib/extract_export_details'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); +jest.mock('../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + jest.mock('../../../lib/find_objects', () => ({ findObjects: jest.fn(), })); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js index e465149b301dcf..97c0d5b89d657b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; - +import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; import { Flyout } from '../flyout'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -48,6 +48,11 @@ jest.mock('../../../../../lib/resolve_saved_objects', () => ({ saveObjects: jest.fn(), })); +jest.mock('../../../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + jest.mock('ui/notify', () => ({})); const defaultProps = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js index d3f5fb19452548..fba249670ce60c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/table.test.js @@ -21,6 +21,7 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { keyCodes } from '@elastic/eui/lib/services'; +import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -28,6 +29,11 @@ jest.mock('ui/chrome', () => ({ addBasePath: () => '', })); +jest.mock('../../../../../../../../../../management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + import { Table } from '../table'; const defaultProps = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js index 43cf8c2a232867..eeddc390037a6a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js @@ -18,7 +18,7 @@ */ import chrome from 'ui/chrome'; -import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management'; +import { setup as managementSetup } from '../../../../../../../../../management/public/legacy'; import React, { PureComponent, Fragment } from 'react'; import PropTypes from 'prop-types'; @@ -79,7 +79,7 @@ export class Table extends PureComponent { constructor(props) { super(props); - this.extraActions = SavedObjectsManagementActionRegistry.get(); + this.extraActions = managementSetup.savedObjects.registry.get(); } onChange = ({ query, error }) => { diff --git a/src/legacy/core_plugins/management/index.ts b/src/legacy/core_plugins/management/index.ts new file mode 100644 index 00000000000000..65601b53718151 --- /dev/null +++ b/src/legacy/core_plugins/management/index.ts @@ -0,0 +1,37 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from '../../../../kibana'; + +// eslint-disable-next-line import/no-default-export +export default function ManagementPlugin(kibana: any) { + const config: Legacy.PluginSpecOptions = { + id: 'management', + publicDir: resolve(__dirname, 'public'), + config: (Joi: any) => { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + init: (server: Legacy.Server) => ({}), + }; + + return new kibana.Plugin(config); +} diff --git a/src/legacy/core_plugins/management/package.json b/src/legacy/core_plugins/management/package.json new file mode 100644 index 00000000000000..77d33a7bce3b6c --- /dev/null +++ b/src/legacy/core_plugins/management/package.json @@ -0,0 +1,5 @@ +{ + "name": "management", + "version": "kibana" +} + \ No newline at end of file diff --git a/src/legacy/ui/public/management/saved_objects_management/index.ts b/src/legacy/core_plugins/management/public/index.ts similarity index 72% rename from src/legacy/ui/public/management/saved_objects_management/index.ts rename to src/legacy/core_plugins/management/public/index.ts index c7223a859ee37d..3d64b6d2aa2bb1 100644 --- a/src/legacy/ui/public/management/saved_objects_management/index.ts +++ b/src/legacy/core_plugins/management/public/index.ts @@ -17,13 +17,24 @@ * under the License. */ -export { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; +/** + * Static np-ready code, re-exported here so consumers can import from + * `src/legacy/core_plugins/management/public` + * + * @public + */ + export { + ManagementSetup, + ManagementStart, + plugin, + IndexPatternCreationConfig, + IndexPatternListConfig, SavedObjectsManagementAction, SavedObjectsManagementRecord, - SavedObjectsManagementRecordReference, -} from './saved_objects_management_action'; +} from './np_ready'; + export { processImportResponse, ProcessedImportResponse, -} from '../../../../core_plugins/kibana/public/management/sections/objects/lib/process_import_response'; +} from '../../kibana/public/management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts new file mode 100644 index 00000000000000..7c17f0c6bddc01 --- /dev/null +++ b/src/legacy/core_plugins/management/public/legacy.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. + */ + +/** + * New Platform Shim + * + * In this file, we import any legacy dependencies we have, and shim them into + * our plugin by manually constructing the values that the new platform will + * eventually be passing to the `setup/start` method of our plugin definition. + * + * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy + * world code. Then when it comes time to migrate to the new platform, we can + * simply delete this shim file. + * + * We are also calling `setup/start` here and exporting our public contract so that + * other legacy plugins are able to import from '../core_plugins/visualizations/legacy' + * and receive the response value of the `setup/start` contract, mimicking the + * data that will eventually be injected by the new platform. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { plugin } from '.'; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, {}); +export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/core_plugins/management/public/np_ready/index.ts b/src/legacy/core_plugins/management/public/np_ready/index.ts new file mode 100644 index 00000000000000..ec93516df8349d --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/index.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/** + * Management Plugin - public + * + * This is the entry point for the entire client-side public contract of the plugin. + * If something is not explicitly exported here, you can safely assume it is private + * to the plugin and not considered stable. + * + * All stateful contracts will be injected by the platform at runtime, and are defined + * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are + * either types, or static code. + */ +import { PluginInitializerContext } from 'src/core/public'; +import { ManagementPlugin } from './plugin'; +export { ManagementSetup, ManagementStart } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ManagementPlugin(initializerContext); +} + +export { + IndexPatternCreationConfig, + IndexPatternListConfig, +} from './services/index_pattern_management'; + +export { + SavedObjectsManagementAction, + SavedObjectsManagementRecord, +} from './services/saved_objects_management'; diff --git a/src/legacy/core_plugins/management/public/np_ready/mocks.ts b/src/legacy/core_plugins/management/public/np_ready/mocks.ts new file mode 100644 index 00000000000000..13a0cf4c891a39 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/mocks.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { coreMock } from '../../../../../core/public/mocks'; +import { ManagementSetup, ManagementStart, ManagementPlugin } from './plugin'; + +const createSetupContract = (): ManagementSetup => ({ + indexPattern: { + creation: { + add: jest.fn(), + getType: jest.fn(), + getIndexPatternCreationOptions: jest.fn(), + } as any, + list: { + add: jest.fn(), + getIndexPatternTags: jest.fn(), + getFieldInfo: jest.fn(), + areScriptedFieldsEnabled: jest.fn(), + } as any, + }, + savedObjects: { + registry: { + register: jest.fn(), + has: jest.fn(), + get: jest.fn(() => []), + }, + }, +}); + +const createStartContract = (): ManagementStart => ({}); + +const createInstance = async () => { + const plugin = new ManagementPlugin({} as PluginInitializerContext); + + const setup = plugin.setup(coreMock.createSetup(), {}); + const doStart = () => plugin.start(coreMock.createStart(), {}); + + return { + plugin, + setup, + doStart, + }; +}; + +export const mockManagementPlugin = { + createSetupContract, + createStartContract, + createInstance, +}; diff --git a/src/legacy/core_plugins/management/public/np_ready/plugin.ts b/src/legacy/core_plugins/management/public/np_ready/plugin.ts new file mode 100644 index 00000000000000..032a46439ba55a --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/plugin.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { IndexPatternManagementService, IndexPatternManagementSetup } from './services'; +import { + SavedObjectsManagementService, + SavedObjectsManagementServiceSetup, +} from './services/saved_objects_management'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ManagementPluginSetupDependencies {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ManagementPluginStartDependencies {} + +export interface ManagementSetup { + indexPattern: IndexPatternManagementSetup; + savedObjects: SavedObjectsManagementServiceSetup; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ManagementStart {} + +export class ManagementPlugin + implements + Plugin< + ManagementSetup, + ManagementStart, + ManagementPluginSetupDependencies, + ManagementPluginStartDependencies + > { + private readonly indexPattern = new IndexPatternManagementService(); + private readonly savedObjects = new SavedObjectsManagementService(); + + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, deps: ManagementPluginSetupDependencies) { + return { + indexPattern: this.indexPattern.setup({ httpClient: core.http }), + savedObjects: this.savedObjects.setup(), + }; + } + + public start(core: CoreStart, plugins: ManagementPluginStartDependencies) { + return {}; + } + + public stop() { + this.indexPattern.stop(); + } +} diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_types.js b/src/legacy/core_plugins/management/public/np_ready/services/index.ts similarity index 87% rename from src/legacy/ui/public/management/index_pattern_creation/index_pattern_types.js rename to src/legacy/core_plugins/management/public/np_ready/services/index.ts index 9e94a52278ab24..4d55fce3d8a7dc 100644 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_types.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export const indexPatternTypes = []; -export const addIndexPatternType = (type) => indexPatternTypes.push(type); +export * from './index_pattern_management'; +export * from './saved_objects_management'; diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts similarity index 55% rename from src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js rename to src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts index 9cf1e6c4ed3735..0598c88c80ba7c 100644 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation_config.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts @@ -19,17 +19,39 @@ import { i18n } from '@kbn/i18n'; -const indexPatternTypeName = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'index pattern' }); - -const indexPatternButtonText = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Standard index pattern' }); - -const indexPatternButtonDescription = i18n.translate('common.ui.management.editIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform full aggregations against any data' }); +const indexPatternTypeName = i18n.translate( + 'management.editIndexPattern.createIndex.defaultTypeName', + { defaultMessage: 'index pattern' } +); + +const indexPatternButtonText = i18n.translate( + 'management.editIndexPattern.createIndex.defaultButtonText', + { defaultMessage: 'Standard index pattern' } +); + +const indexPatternButtonDescription = i18n.translate( + 'management.editIndexPattern.createIndex.defaultButtonDescription', + { defaultMessage: 'Perform full aggregations against any data' } +); + +export type UrlHandler = (url: string) => void; + +export interface IndexPatternCreationOption { + text: string; + description: string; + testSubj: string; + onClick: () => void; + isBeta?: boolean; +} export class IndexPatternCreationConfig { - static key = 'default'; + public readonly key = 'default'; + + protected type?: string; + protected name: string; + protected showSystemIndices: boolean; + protected httpClient: object | null; + protected isBeta: boolean; constructor({ type = undefined, @@ -37,6 +59,12 @@ export class IndexPatternCreationConfig { showSystemIndices = true, httpClient = null, isBeta = false, + }: { + type?: string; + name?: string; + showSystemIndices?: boolean; + httpClient?: object | null; + isBeta?: boolean; }) { this.type = type; this.name = name; @@ -45,7 +73,9 @@ export class IndexPatternCreationConfig { this.isBeta = isBeta; } - async getIndexPatternCreationOption(urlHandler) { + public async getIndexPatternCreationOption( + urlHandler: UrlHandler + ): Promise { return { text: indexPatternButtonText, description: indexPatternButtonDescription, @@ -56,39 +86,39 @@ export class IndexPatternCreationConfig { }; } - getIndexPatternType = () => { + public getIndexPatternType() { return this.type; } - getIndexPatternName = () => { + public getIndexPatternName() { return this.name; } - getIsBeta = () => { + public getIsBeta() { return this.isBeta; } - getShowSystemIndices = () => { + public getShowSystemIndices() { return this.showSystemIndices; } - getIndexTags() { + public getIndexTags() { return []; } - checkIndicesForErrors = () => { + public checkIndicesForErrors() { return undefined; } - getIndexPatternMappings = () => { + public getIndexPatternMappings() { return {}; } - renderPrompt = () => { + public renderPrompt() { return null; } - getFetchForWildcardOptions = () => { + public getFetchForWildcardOptions() { return {}; } } diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts similarity index 81% rename from src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js rename to src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts index 9b1bfca284ee67..84c4c28aa2260f 100644 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config_registry.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts @@ -17,10 +17,5 @@ * under the License. */ -import { uiRegistry } from 'ui/registry/_registry'; - -export const IndexPatternListConfigRegistry = uiRegistry({ - name: 'indexPatternList', - index: ['name'], - order: ['order'], -}); +export { IndexPatternCreationConfig } from './config'; +export { IndexPatternCreationManager } from './manager'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts new file mode 100644 index 00000000000000..605ffdd6f21349 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts @@ -0,0 +1,61 @@ +/* + * 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 { HttpServiceBase } from '../../../../../../../../core/public'; +import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; + +export class IndexPatternCreationManager { + private configs: IndexPatternCreationConfig[]; + + constructor(private readonly httpClient: HttpServiceBase) { + this.configs = []; + } + + public add(Config: typeof IndexPatternCreationConfig) { + const config = new Config({ httpClient: this.httpClient }); + if (this.configs.findIndex(c => c.key === config.key) !== -1) { + throw new Error(`${config.key} exists in IndexPatternCreationManager.`); + } + this.configs.push(config); + } + + public getType(key: string | undefined): IndexPatternCreationConfig | null { + if (key) { + const index = this.configs.findIndex(config => config.key === key); + return this.configs[index] || null; + } else { + return this.getType('default'); + } + } + + public async getIndexPatternCreationOptions(urlHandler: UrlHandler) { + const options: IndexPatternCreationOption[] = []; + await Promise.all( + this.configs.map(async config => { + const option = config.getIndexPatternCreationOption + ? await config.getIndexPatternCreationOption(urlHandler) + : null; + if (option) { + options.push(option); + } + }) + ); + return options; + } +} diff --git a/src/legacy/ui/public/management/index_pattern_creation/register.js b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts similarity index 80% rename from src/legacy/ui/public/management/index_pattern_creation/register.js rename to src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts index bca4387b496fd8..2abe13eb0e2927 100644 --- a/src/legacy/ui/public/management/index_pattern_creation/register.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts @@ -17,7 +17,6 @@ * under the License. */ -import { IndexPatternCreationConfig } from './index_pattern_creation_config'; -import { addIndexPatternType } from './index_pattern_types'; - -addIndexPatternType(IndexPatternCreationConfig); +export * from './index_pattern_management_service'; +export { IndexPatternCreationConfig } from './creation'; +export { IndexPatternListConfig } from './list'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts new file mode 100644 index 00000000000000..b9e07564a324cb --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts @@ -0,0 +1,53 @@ +/* + * 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 { HttpServiceBase } from '../../../../../../../core/public'; +import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; +import { IndexPatternListManager, IndexPatternListConfig } from './list'; + +interface SetupDependencies { + httpClient: HttpServiceBase; +} + +/** + * Index patterns management service + * + * @internal + */ +export class IndexPatternManagementService { + public setup({ httpClient }: SetupDependencies) { + const creation = new IndexPatternCreationManager(httpClient); + const list = new IndexPatternListManager(); + + creation.add(IndexPatternCreationConfig); + list.add(IndexPatternListConfig); + + return { + creation, + list, + }; + } + + public stop() { + // nothing to do here yet. + } +} + +/** @internal */ +export type IndexPatternManagementSetup = ReturnType; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts new file mode 100644 index 00000000000000..dd4d77a6811717 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts @@ -0,0 +1,51 @@ +/* + * 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 { IIndexPattern, IFieldType } from 'src/plugins/data/public'; + +export interface IndexPatternTag { + key: string; + name: string; +} + +export class IndexPatternListConfig { + public readonly key = 'default'; + + public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean): IndexPatternTag[] { + return isDefault + ? [ + { + key: 'default', + name: i18n.translate('management.editIndexPattern.list.defaultIndexPatternListName', { + defaultMessage: 'Default', + }), + }, + ] + : []; + } + + public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + return []; + } + + public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + return true; + } +} diff --git a/src/legacy/ui/public/management/index_pattern_list/register.js b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts similarity index 77% rename from src/legacy/ui/public/management/index_pattern_list/register.js rename to src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts index 6488ddd399b7b9..114226b3a45708 100644 --- a/src/legacy/ui/public/management/index_pattern_list/register.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts @@ -17,7 +17,5 @@ * under the License. */ -import { IndexPatternListConfig } from './index_pattern_list_config'; -import { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry'; - -IndexPatternListConfigRegistry.register(() => IndexPatternListConfig); +export { IndexPatternListConfig } from './config'; +export { IndexPatternListManager } from './manager'; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts new file mode 100644 index 00000000000000..73ca33ae914a9b --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IIndexPattern, IFieldType } from 'src/plugins/data/public'; +import { IndexPatternListConfig, IndexPatternTag } from './config'; + +export class IndexPatternListManager { + private configs: IndexPatternListConfig[]; + + constructor() { + this.configs = []; + } + + public add(Config: typeof IndexPatternListConfig) { + const config = new Config(); + if (this.configs.findIndex(c => c.key === config.key) !== -1) { + throw new Error(`${config.key} exists in IndexPatternListManager.`); + } + this.configs.push(config); + } + + public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { + return this.configs.reduce((tags: IndexPatternTag[], config) => { + return config.getIndexPatternTags + ? tags.concat(config.getIndexPatternTags(indexPattern, isDefault)) + : tags; + }, []); + } + + public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + return this.configs.reduce((info: string[], config) => { + return config.getFieldInfo ? info.concat(config.getFieldInfo(indexPattern, field)) : info; + }, []); + } + + public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + return this.configs.every(config => { + return config.areScriptedFieldsEnabled ? config.areScriptedFieldsEnabled(indexPattern) : true; + }); + } +} diff --git a/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts new file mode 100644 index 00000000000000..bad3b3ac36ef74 --- /dev/null +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/index.ts @@ -0,0 +1,22 @@ +/* + * 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 * from './saved_objects_management_action_registry'; +export * from './saved_objects_management_action'; +export * from './saved_objects_management_service'; diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts similarity index 96% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts index a09f842e367133..d83afb195a492d 100644 --- a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action.ts +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ReactNode } from '@elastic/eui/node_modules/@types/react'; +import { ReactNode } from 'react'; export interface SavedObjectsManagementRecordReference { type: string; diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.test.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.test.ts similarity index 100% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.test.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.test.ts diff --git a/src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.ts b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.ts similarity index 100% rename from src/legacy/ui/public/management/saved_objects_management/saved_objects_management_action_registry.ts rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_action_registry.ts diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config.js b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts similarity index 68% rename from src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config.js rename to src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts index cf1389fbab9f5e..d5e90d12cccc91 100644 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list_config.js +++ b/src/legacy/core_plugins/management/public/np_ready/services/saved_objects_management/saved_objects_management_service.ts @@ -16,22 +16,17 @@ * specific language governing permissions and limitations * under the License. */ +import { SavedObjectsManagementActionRegistry } from './saved_objects_management_action_registry'; -export class IndexPatternListConfig { - static key = 'default'; - - getIndexPatternTags = (indexPattern, isDefault) => { - return isDefault ? [{ - key: 'default', - name: 'Default', - }] : []; - } - - getFieldInfo = () => { - return []; +export class SavedObjectsManagementService { + public setup() { + return { + registry: SavedObjectsManagementActionRegistry, + }; } - areScriptedFieldsEnabled = () => { - return true; - } + public stop() {} } + +/** @internal */ +export type SavedObjectsManagementServiceSetup = ReturnType; diff --git a/src/legacy/ui/public/management/index_pattern_creation/index.js b/src/legacy/ui/public/management/index_pattern_creation/index.js deleted file mode 100644 index 0b677cbfd1f643..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/index.js +++ /dev/null @@ -1,23 +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 './register'; -export { IndexPatternCreationFactory } from './index_pattern_creation'; -export { IndexPatternCreationConfig } from './index_pattern_creation_config'; -export { indexPatternTypes, addIndexPatternType } from './index_pattern_types'; diff --git a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js b/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js deleted file mode 100644 index 12cecc956ab680..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_creation/index_pattern_creation.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { indexPatternTypes } from './index_pattern_types'; - -class IndexPatternCreation { - constructor(httpClient, type) { - this._allTypes = indexPatternTypes.map(Plugin => new Plugin({ httpClient })); - this._setCurrentType(type); - } - - _setCurrentType = (type) => { - const index = type ? indexPatternTypes.findIndex(Plugin => Plugin.key === type) : -1; - this._currentType = index > -1 && this._allTypes[index] ? this._allTypes[index] : null; - } - - getType = () => { - return this._currentType || null; - } - - getIndexPatternCreationOptions = async (urlHandler) => { - const options = []; - await Promise.all(this._allTypes.map(async type => { - const option = type.getIndexPatternCreationOption ? await type.getIndexPatternCreationOption(urlHandler) : null; - if(option) { - options.push(option); - } - })); - return options; - } -} - -export const IndexPatternCreationFactory = (Private, $http) => { - return (type = 'default') => { - const indexPatternCreationProvider = new IndexPatternCreation($http, type); - return indexPatternCreationProvider; - }; -}; diff --git a/src/legacy/ui/public/management/index_pattern_list/index.js b/src/legacy/ui/public/management/index_pattern_list/index.js deleted file mode 100644 index a77a3f1f61f2ce..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/index.js +++ /dev/null @@ -1,23 +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 './register'; -export { IndexPatternListFactory } from './index_pattern_list'; -export { IndexPatternListConfig } from './index_pattern_list_config'; -export { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry'; diff --git a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js b/src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js deleted file mode 100644 index 9ff82db27fefce..00000000000000 --- a/src/legacy/ui/public/management/index_pattern_list/index_pattern_list.js +++ /dev/null @@ -1,52 +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 { IndexPatternListConfigRegistry } from './index_pattern_list_config_registry'; - -class IndexPatternList { - constructor(registry) { - this._plugins = registry.inOrder.map(Plugin => new Plugin()); - } - - getIndexPatternTags = (indexPattern, isDefault) => { - return this._plugins.reduce((tags, plugin) => { - return plugin.getIndexPatternTags ? tags.concat(plugin.getIndexPatternTags(indexPattern, isDefault)) : tags; - }, []); - } - - getFieldInfo = (indexPattern, field) => { - return this._plugins.reduce((info, plugin) => { - return plugin.getFieldInfo ? info.concat(plugin.getFieldInfo(indexPattern, field)) : info; - }, []); - } - - areScriptedFieldsEnabled = (indexPattern) => { - return this._plugins.every((plugin) => { - return plugin.areScriptedFieldsEnabled ? plugin.areScriptedFieldsEnabled(indexPattern) : true; - }); - } -} - -export const IndexPatternListFactory = (Private) => { - return function () { - const indexPatternListRegistry = Private(IndexPatternListConfigRegistry); - const indexPatternListProvider = new IndexPatternList(indexPatternListRegistry); - return indexPatternListProvider; - }; -}; diff --git a/src/legacy/ui/public/management/section.js b/src/legacy/ui/public/management/section.js index d5e70c03d42595..b6952695cd910d 100644 --- a/src/legacy/ui/public/management/section.js +++ b/src/legacy/ui/public/management/section.js @@ -42,7 +42,7 @@ export class ManagementSection { this.id = id; this.items = new IndexedArray({ index: ['id'], - order: ['order'] + order: ['order'], }); this.visible = true; this.disabled = false; @@ -51,13 +51,14 @@ export class ManagementSection { this.url = ''; assign(this, options); - } get visibleItems() { return this.items.inOrder.filter(item => { const capabilityManagementSection = capabilities.get().management[this.id]; - const itemCapability = capabilityManagementSection ? capabilityManagementSection[item.id] : null; + const itemCapability = capabilityManagementSection + ? capabilityManagementSection[item.id] + : null; return item.visible && itemCapability !== false; }); @@ -95,10 +96,10 @@ export class ManagementSection { } /** - * Deregisters a section - * - * @param {string} id - */ + * Deregisters a section + * + * @param {string} id + */ deregister(id) { this.items.remove(item => item.id === id); listeners.forEach(fn => fn(this.items)); diff --git a/src/legacy/ui/public/management/section.test.js b/src/legacy/ui/public/management/section.test.js index a45fca426e2b42..e6363f2e164b48 100644 --- a/src/legacy/ui/public/management/section.test.js +++ b/src/legacy/ui/public/management/section.test.js @@ -24,10 +24,10 @@ jest.mock('ui/capabilities', () => ({ kibana: { sampleFeature1: true, sampleFeature2: false, - } - } - }) - } + }, + }, + }), + }, })); import { ManagementSection } from './section'; @@ -115,7 +115,9 @@ describe('ManagementSection', () => { it('calls listener when item added', () => { let listerCalled = false; - const listenerFn = () => { listerCalled = true; }; + const listenerFn = () => { + listerCalled = true; + }; section.addListener(listenerFn); section.register('about'); @@ -131,12 +133,12 @@ describe('ManagementSection', () => { section.register('about'); }); - it ('deregisters an existing section', () => { + it('deregisters an existing section', () => { section.deregister('about'); expect(section.items).toHaveLength(0); }); - it ('allows deregistering a section more than once', () => { + it('allows deregistering a section more than once', () => { section.deregister('about'); section.deregister('about'); expect(section.items).toHaveLength(0); @@ -144,7 +146,9 @@ describe('ManagementSection', () => { it('calls listener when item added', () => { let listerCalled = false; - const listenerFn = () => { listerCalled = true; }; + const listenerFn = () => { + listerCalled = true; + }; section.addListener(listenerFn); section.deregister('about'); @@ -202,7 +206,9 @@ describe('ManagementSection', () => { }); it('can be ordered', () => { - const ids = section.items.inOrder.map((i) => { return i.id; }); + const ids = section.items.inOrder.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'two', 'three']); }); }); @@ -256,20 +262,26 @@ describe('ManagementSection', () => { }); it('maintains the order', () => { - const ids = section.visibleItems.map((i) => { return i.id; }); + const ids = section.visibleItems.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'two', 'three']); }); it('does not include hidden items', () => { section.getSection('two').hide(); - const ids = section.visibleItems.map((i) => { return i.id; }); + const ids = section.visibleItems.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'three']); }); it('does not include visible items hidden via uiCapabilities', () => { section.register('sampleFeature2', { order: 4, visible: true }); - const ids = section.visibleItems.map((i) => { return i.id; }); + const ids = section.visibleItems.map(i => { + return i.id; + }); expect(ids).toEqual(['one', 'two', 'three']); }); }); diff --git a/src/legacy/ui/public/management/sections_register.js b/src/legacy/ui/public/management/sections_register.js index b25b381eef67b0..a63fd390ea3caa 100644 --- a/src/legacy/ui/public/management/sections_register.js +++ b/src/legacy/ui/public/management/sections_register.js @@ -22,15 +22,15 @@ import { i18n } from '@kbn/i18n'; export const management = new ManagementSection('management', { display: i18n.translate('common.ui.management.displayName', { - defaultMessage: 'Management' - }) + defaultMessage: 'Management', + }), }); management.register('data', { display: i18n.translate('common.ui.management.connectDataDisplayName', { - defaultMessage: 'Connect Data' + defaultMessage: 'Connect Data', }), - order: 0 + order: 0, }); management.register('elasticsearch', { diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js index b14531d8f6efda..9a3aed548dcc9c 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { addIndexPatternType } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy'; import { RollupIndexPatternCreationConfig } from './rollup_index_pattern_creation_config'; export function initIndexPatternCreation() { - addIndexPatternType(RollupIndexPatternCreationConfig); + managementSetup.indexPattern.creation.add(RollupIndexPatternCreationConfig); } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index 104d632e3ac3d2..8448f9da19f458 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -5,32 +5,44 @@ */ import React from 'react'; -import { IndexPatternCreationConfig } from 'ui/management/index_pattern_creation'; +import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public'; import { RollupPrompt } from './components/rollup_prompt'; import { setHttpClient, getRollupIndices } from '../services/api'; import { i18n } from '@kbn/i18n'; -const rollupIndexPatternTypeName = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'rollup index pattern' }); +const rollupIndexPatternTypeName = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', + { defaultMessage: 'rollup index pattern' } +); -const rollupIndexPatternButtonText = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Rollup index pattern' }); +const rollupIndexPatternButtonText = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText', + { defaultMessage: 'Rollup index pattern' } +); -const rollupIndexPatternButtonDescription = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform limited aggregations against summarized data' }); +const rollupIndexPatternButtonDescription = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription', + { defaultMessage: 'Perform limited aggregations against summarized data' } +); -const rollupIndexPatternNoMatchError = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError', - { defaultMessage: 'Rollup index pattern error: must match one rollup index' }); +const rollupIndexPatternNoMatchError = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError', + { defaultMessage: 'Rollup index pattern error: must match one rollup index' } +); -const rollupIndexPatternTooManyMatchesError = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError', - { defaultMessage: 'Rollup index pattern error: can only match one rollup index' }); +const rollupIndexPatternTooManyMatchesError = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError', + { defaultMessage: 'Rollup index pattern error: can only match one rollup index' } +); -const rollupIndexPatternIndexLabel = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel', - { defaultMessage: 'Rollup' }); +const rollupIndexPatternIndexLabel = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel', + { defaultMessage: 'Rollup' } +); export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig { - static key = 'rollup'; + key = 'rollup'; constructor(options) { super({ @@ -60,76 +72,85 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig async getIndexPatternCreationOption(urlHandler) { await this.settingUp; - return this.rollupIndices && this.rollupIndices.length ? { - text: rollupIndexPatternButtonText, - description: rollupIndexPatternButtonDescription, - testSubj: `createRollupIndexPatternButton`, - isBeta: this.isBeta, - onClick: () => { - urlHandler('/management/kibana/index_pattern?type=rollup'); - }, - } : null; + return this.rollupIndices && this.rollupIndices.length + ? { + text: rollupIndexPatternButtonText, + description: rollupIndexPatternButtonDescription, + testSubj: `createRollupIndexPatternButton`, + isBeta: this.isBeta, + onClick: () => { + urlHandler('/management/kibana/index_pattern?type=rollup'); + }, + } + : null; } - isRollupIndex = (indexName) => { + isRollupIndex = indexName => { return this.rollupIndices.includes(indexName); - } + }; getIndexTags(indexName) { - return this.isRollupIndex(indexName) ? [{ - key: this.type, - name: rollupIndexPatternIndexLabel, - }] : []; + return this.isRollupIndex(indexName) + ? [ + { + key: this.type, + name: rollupIndexPatternIndexLabel, + }, + ] + : []; } - checkIndicesForErrors = (indices) => { + checkIndicesForErrors = indices => { this.rollupIndex = null; - if(!indices || !indices.length) { + if (!indices || !indices.length) { return; } const rollupIndices = indices.filter(index => this.isRollupIndex(index.name)); - if(!rollupIndices.length) { + if (!rollupIndices.length) { return [rollupIndexPatternNoMatchError]; - } else if(rollupIndices.length > 1) { + } else if (rollupIndices.length > 1) { return [rollupIndexPatternTooManyMatchesError]; } const rollupIndexName = rollupIndices[0].name; const error = this.rollupIndicesCapabilities[rollupIndexName].error; - if(error) { - const errorMessage = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError', { - defaultMessage: 'Rollup index pattern error: {error}', - values: { - error + if (error) { + const errorMessage = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError', + { + defaultMessage: 'Rollup index pattern error: {error}', + values: { + error, + }, } - }); + ); return [errorMessage]; } this.rollupIndex = rollupIndexName; - } + }; getIndexPatternMappings = () => { - return this.rollupIndex ? { - type: this.type, - typeMeta: { - params: { - rollup_index: this.rollupIndex, + return this.rollupIndex + ? { + type: this.type, + typeMeta: { + params: { + rollup_index: this.rollupIndex, + }, + aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, }, - aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, - }, - } : {}; - } + } + : {}; + }; renderPrompt = () => { - return ( - - ); - } + return ; + }; getFetchForWildcardOptions = () => { return { @@ -138,5 +159,5 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig rollup_index: this.rollupIndex, }, }; - } + }; } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js index d5ee0d8c6976ee..173c28826436bc 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternListConfigRegistry } from 'ui/management/index_pattern_list'; +import { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy'; import { RollupIndexPatternListConfig } from './rollup_index_pattern_list_config'; export function initIndexPatternList() { - IndexPatternListConfigRegistry.register(() => RollupIndexPatternListConfig); + managementSetup.indexPattern.list.add(RollupIndexPatternListConfig); } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js index 92c3ac20a28197..1d8ae6d4250656 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js @@ -3,49 +3,59 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternListConfig } from 'ui/management/index_pattern_list'; +import { IndexPatternListConfig } from '../../../../../../src/legacy/core_plugins/management/public'; function isRollup(indexPattern) { - return indexPattern.type === 'rollup' || (indexPattern.get && indexPattern.get('type') === 'rollup'); + return ( + indexPattern.type === 'rollup' || (indexPattern.get && indexPattern.get('type') === 'rollup') + ); } export class RollupIndexPatternListConfig extends IndexPatternListConfig { - static key = 'rollup'; - - getIndexPatternTags = (indexPattern) => { - return isRollup(indexPattern) ? [{ - key: 'rollup', - name: 'Rollup', - }] : []; - } + key = 'rollup'; + + getIndexPatternTags = indexPattern => { + return isRollup(indexPattern) + ? [ + { + key: 'rollup', + name: 'Rollup', + }, + ] + : []; + }; getFieldInfo = (indexPattern, field) => { - if(!isRollup(indexPattern)) { + if (!isRollup(indexPattern)) { return []; } const allAggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs; const fieldAggs = allAggs && Object.keys(allAggs).filter(agg => allAggs[agg][field]); - if(!fieldAggs || !fieldAggs.length) { + if (!fieldAggs || !fieldAggs.length) { return []; } - return ['Rollup aggregations:'].concat(fieldAggs.map(aggName => { - const agg = allAggs[aggName][field]; - switch(aggName) { - case 'date_histogram': - return `${aggName} (interval: ${agg.interval}, ${agg.delay ? `delay: ${agg.delay},` : ''} ${agg.time_zone})`; - break; - case 'histogram': - return `${aggName} (interval: ${agg.interval})`; - default: - return aggName; - } - })); - } - - areScriptedFieldsEnabled = (indexPattern) => { + return ['Rollup aggregations:'].concat( + fieldAggs.map(aggName => { + const agg = allAggs[aggName][field]; + switch (aggName) { + case 'date_histogram': + return `${aggName} (interval: ${agg.interval}, ${ + agg.delay ? `delay: ${agg.delay},` : '' + } ${agg.time_zone})`; + break; + case 'histogram': + return `${aggName} (interval: ${agg.interval})`; + default: + return aggName; + } + }) + ); + }; + + areScriptedFieldsEnabled = indexPattern => { return !isRollup(indexPattern); - } + }; } diff --git a/x-pack/legacy/plugins/rollup/public/services/api.js b/x-pack/legacy/plugins/rollup/public/services/api.js index 59b26c45928481..ae9e8756c7efc9 100644 --- a/x-pack/legacy/plugins/rollup/public/services/api.js +++ b/x-pack/legacy/plugins/rollup/public/services/api.js @@ -3,15 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import chrome from 'ui/chrome'; let httpClient; -export const setHttpClient = (client) => { +export const setHttpClient = client => { httpClient = client; }; -const apiPrefix = chrome.addBasePath('/api/rollup'); export async function getRollupIndices() { - const response = await httpClient.get(`${apiPrefix}/indices`); - return response.data || {}; + const response = await httpClient.get('/api/rollup/indices'); + return response || {}; } diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index 4ed1937ebf782e..e0db5f360f0f6d 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; import { SavedObjectsManagementAction, SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; -import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; +} from '../../../../../../../src/legacy/core_plugins/management/public'; import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../spaces_manager'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts index 7517fa48ad8b81..0352902072790e 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from 'ui/management/saved_objects_management'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts index 7eddf3f4891e5a..8807489157d71e 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ProcessedImportResponse, - SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; export interface SummarizedSavedObjectResult { type: string; diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts index c1672e65326aa7..67d34960ed98e3 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts @@ -5,7 +5,7 @@ */ import { EventEmitter } from 'events'; import { kfetch } from 'ui/kfetch'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx index f461100db01db8..c30792b23e3ac8 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; @@ -18,6 +19,11 @@ import { spacesManagerMock } from '../../../../lib/mocks'; import { SpacesManager } from '../../../../lib'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +jest.mock('../../../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + interface SetupOpts { mockSpaces?: Space[]; returnBeforeSpacesLoad?: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx index 7461edcff10e9f..1de5a10977f838 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx @@ -21,12 +21,12 @@ import { import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { - SavedObjectsManagementRecord, - processImportResponse, ProcessedImportResponse, -} from 'ui/management/saved_objects_management'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; + processImportResponse, +} from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../../../common/model/space'; import { SpacesManager } from '../../../../lib'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx index f8d6fdf85205e8..5853bebe3c6697 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx @@ -5,10 +5,10 @@ */ import React, { Fragment } from 'react'; -import { ProcessedImportResponse } from 'ui/management/saved_objects_management'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx index 1b712e84d4a056..b04c9598559b3e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx @@ -5,10 +5,6 @@ */ import React, { Fragment } from 'react'; -import { - ProcessedImportResponse, - SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; import { EuiSpacer, EuiText, @@ -17,6 +13,8 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { summarizeCopyResult } from '../../../../lib/copy_saved_objects_to_space'; import { Space } from '../../../../../common/model/space'; import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx index b27be4d1715e85..f71be12276be51 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; import { SpaceAvatar } from '../../../../components'; import { Space } from '../../../../../common/model/space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx index 43639641d541c7..66ec38331c89ae 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx @@ -5,10 +5,10 @@ */ import React from 'react'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; import { SummarizedCopyToSpaceResult } from 'plugins/spaces/lib/copy_saved_objects_to_space'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../../../common/model/space'; import { CopyStatusIndicator } from './copy_status_indicator'; import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx index 179665ed11111c..f659154c910f19 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx @@ -12,9 +12,9 @@ import { PAGE_TITLE_COMPONENT, registerSettingsComponent, } from 'ui/management'; -import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management'; // @ts-ignore import routes from 'ui/routes'; +import { setup as managementSetup } from '../../../../../../../src/legacy/core_plugins/management/public/legacy'; import { SpacesManager } from '../../lib'; import { AdvancedSettingsSubtitle } from './components/advanced_settings_subtitle'; import { AdvancedSettingsTitle } from './components/advanced_settings_title'; @@ -54,8 +54,8 @@ routes.defaults(/\/management/, { ); // This route resolve function executes any time the management screen is loaded, and we want to ensure // that this action is only registered once. - if (!SavedObjectsManagementActionRegistry.has(action.id)) { - SavedObjectsManagementActionRegistry.register(action); + if (!managementSetup.savedObjects.registry.has(action.id)) { + managementSetup.savedObjects.registry.register(action); } // Customize Advanced Settings diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9b9a842689fd48..b9142c821051f2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -463,9 +463,9 @@ "common.ui.management.breadcrumb": "管理", "common.ui.management.connectDataDisplayName": "データに接続", "common.ui.management.displayName": "管理", - "common.ui.management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", - "common.ui.management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", - "common.ui.management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", + "management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", + "management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", + "management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", "common.ui.management.nav.menu": "管理メニュー", "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 91dc516eebaad6..109693bbb4b1b4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -463,9 +463,9 @@ "common.ui.management.breadcrumb": "管理", "common.ui.management.connectDataDisplayName": "连接数据", "common.ui.management.displayName": "管理", - "common.ui.management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", - "common.ui.management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", - "common.ui.management.editIndexPattern.createIndex.defaultTypeName": "索引模式", + "management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", + "management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", + "management.editIndexPattern.createIndex.defaultTypeName": "索引模式", "common.ui.management.nav.menu": "管理菜单", "common.ui.modals.cancelButtonLabel": "取消", "common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index f4b2be34202980..bf3435582ef474 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -153,10 +153,10 @@ export default async function ({ readConfigFile }) { pathname: '/app/uptime', }, apm: { - pathname: '/app/apm' + pathname: '/app/apm', }, ml: { - pathname: '/app/ml' + pathname: '/app/ml', }, rollupJob: { pathname: '/app/kibana', From 7a629466c1d2640b39dd674d82c7947fae1842ae Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 9 Dec 2019 13:09:41 -0500 Subject: [PATCH 12/36] [Lens] Show keyword fields for pre-7.3 index patterns (#52410) --- .../public/indexpattern_plugin/loader.test.ts | 24 ++++++++++++++++++- .../lens/public/indexpattern_plugin/loader.ts | 5 +--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts index 03bbddb998b3c1..72cbd1b861a05e 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts @@ -129,7 +129,19 @@ const sampleIndexPatterns = { }; function indexPatternSavedObject({ id }: { id: keyof typeof sampleIndexPatterns }) { - const pattern = sampleIndexPatterns[id]; + const pattern = { + ...sampleIndexPatterns[id], + fields: [ + ...sampleIndexPatterns[id].fields, + { + name: 'description', + type: 'string', + aggregatable: false, + searchable: true, + esTypes: ['text'], + }, + ], + }; return { id, type: 'index-pattern', @@ -184,6 +196,16 @@ describe('loader', () => { expect(cache).toMatchObject(sampleIndexPatterns); }); + it('should not allow full text fields', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['a', 'b'], + savedObjectsClient: mockClient(), + }); + + expect(cache).toMatchObject(sampleIndexPatterns); + }); + it('should apply field restrictions from typeMeta', async () => { const cache = await loadIndexPatterns({ cache: {}, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts index 40d9d25869c7a7..89d4224a7df14a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts @@ -284,10 +284,7 @@ function fromSavedObject( type, title: attributes.title, fields: (JSON.parse(attributes.fields) as IndexPatternField[]) - .filter( - ({ type: fieldType, esTypes }) => - fieldType !== 'string' || (esTypes && esTypes.includes('keyword')) - ) + .filter(({ aggregatable }) => !!aggregatable) .concat(documentField), typeMeta: attributes.typeMeta ? (JSON.parse(attributes.typeMeta) as SavedRestrictionsInfo) From 94b2eb49085b5f9021e420cbba7c6aea95fcad6f Mon Sep 17 00:00:00 2001 From: Poff Poffenberger Date: Mon, 9 Dec 2019 12:39:06 -0600 Subject: [PATCH 13/36] [Canvas] Add simple visual test for fullscreen (#51234) * Add simple visual test for fullscreen workpads in Canvas * Adding canvas to the config * Adding sample data visual tests * Refactor to use new helper method * Reverting sample data visual test for now * Forgot to add the awaits --- .../functional/page_objects/canvas_page.ts | 14 +++++++++ x-pack/test/visual_regression/config.js | 1 + .../tests/canvas/fullscreen.js | 24 +++++++++++++++ .../visual_regression/tests/canvas/index.js | 29 +++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 x-pack/test/visual_regression/tests/canvas/fullscreen.js create mode 100644 x-pack/test/visual_regression/tests/canvas/index.js diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index 78863f91451f6a..a4b4f500b8832c 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -11,8 +11,22 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function CanvasPageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); + const browser = getService('browser'); return { + async enterFullscreen() { + const elem = await find.byCssSelector('[aria-label="View fullscreen"]', 20000); + await elem.click(); + }, + + async exitFullscreen() { + await browser.pressKeys(browser.keys.ESCAPE); + }, + + async waitForWorkpadElements() { + await testSubjects.findAll('canvasWorkpadPage > canvasWorkpadPageElementContent'); + }, + async expectCreateWorkpadButtonEnabled() { const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); diff --git a/x-pack/test/visual_regression/config.js b/x-pack/test/visual_regression/config.js index 4f77098d6a4565..ad9a714ee8d02f 100644 --- a/x-pack/test/visual_regression/config.js +++ b/x-pack/test/visual_regression/config.js @@ -13,6 +13,7 @@ export default async function ({ readConfigFile }) { ...functionalConfig.getAll(), testFiles: [ + require.resolve('./tests/canvas'), require.resolve('./tests/login_page'), require.resolve('./tests/maps'), require.resolve('./tests/infra'), diff --git a/x-pack/test/visual_regression/tests/canvas/fullscreen.js b/x-pack/test/visual_regression/tests/canvas/fullscreen.js new file mode 100644 index 00000000000000..998fbb2c21f400 --- /dev/null +++ b/x-pack/test/visual_regression/tests/canvas/fullscreen.js @@ -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. +*/ + +export default function ({ getPageObjects, getService }) { + const PageObjects = getPageObjects(['common', 'canvas']); + const visualTesting = getService('visualTesting'); + + describe('fullscreen', () => { + it('workpad should display properly in fullscreen mode', async () => { + await PageObjects.common.navigateToApp('canvas', { + hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1' + }); + + await PageObjects.canvas.enterFullscreen(); + + await PageObjects.canvas.waitForWorkpadElements(); + + await visualTesting.snapshot(); + }); + }); +} diff --git a/x-pack/test/visual_regression/tests/canvas/index.js b/x-pack/test/visual_regression/tests/canvas/index.js new file mode 100644 index 00000000000000..29bfd07c525cd5 --- /dev/null +++ b/x-pack/test/visual_regression/tests/canvas/index.js @@ -0,0 +1,29 @@ +/* +* 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 { DEFAULT_OPTIONS } from '../../../../../test/visual_regression/services/visual_testing/visual_testing'; + +const [SCREEN_WIDTH] = DEFAULT_OPTIONS.widths || []; + +export default function ({ loadTestFile, getService }) { + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('canvas app visual regression', function () { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('canvas/default'); + + await browser.setWindowSize(SCREEN_WIDTH, 1000); + }); + + after(async () => { + await esArchiver.unload('canvas/default'); + }); + + this.tags('ciGroup10'); + loadTestFile(require.resolve('./fullscreen')); + }); +} From 419ea47cca7086dce1d358ba9889d8dd2d106ed5 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Mon, 9 Dec 2019 19:49:15 +0100 Subject: [PATCH 14/36] Upgrade typescript-eslint to 2.10.0 (#52528) --- package.json | 4 +-- packages/eslint-config-kibana/package.json | 4 +-- yarn.lock | 40 +++++++++++----------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index cf36d4ce884acb..edd2b621296276 100644 --- a/package.json +++ b/package.json @@ -360,8 +360,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index ee65a1cf79148d..7917297883b033 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/yarn.lock b/yarn.lock index 6df7ab3d4243dd..cfef1bec7e6a05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4238,24 +4238,24 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.9.0.tgz#fa810282c0e45f6c2310b9c0dfcd25bff97ab7e9" - integrity sha512-98rfOt3NYn5Gr9wekTB8TexxN6oM8ZRvYuphPs1Atfsy419SDLYCaE30aJkRiiTCwGEY98vOhFsEVm7Zs4toQQ== +"@typescript-eslint/eslint-plugin@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.10.0.tgz#c4cb103275e555e8a7e9b3d14c5951eb6d431e70" + integrity sha512-rT51fNLW0u3fnDGnAHVC5nu+Das+y2CpW10yqvf6/j5xbuUV3FxA3mBaIbM24CXODXjbgUznNb4Kg9XZOUxKAw== dependencies: - "@typescript-eslint/experimental-utils" "2.9.0" + "@typescript-eslint/experimental-utils" "2.10.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.9.0.tgz#bbe99a8d9510240c055fc4b17230dd0192ba3c7f" - integrity sha512-0lOLFdpdJsCMqMSZT7l7W2ta0+GX8A3iefG3FovJjrX+QR8y6htFlFdU7aOVPL6pDvt6XcsOb8fxk5sq+girTw== +"@typescript-eslint/experimental-utils@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.10.0.tgz#8db1656cdfd3d9dcbdbf360b8274dea76f0b2c2c" + integrity sha512-FZhWq6hWWZBP76aZ7bkrfzTMP31CCefVIImrwP3giPLcoXocmLTmr92NLZxuIcTL4GTEOE33jQMWy9PwelL+yQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.9.0" + "@typescript-eslint/typescript-estree" "2.10.0" eslint-scope "^5.0.0" "@typescript-eslint/experimental-utils@^1.13.0": @@ -4267,14 +4267,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" -"@typescript-eslint/parser@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.9.0.tgz#2e9cf438de119b143f642a3a406e1e27eb70b7cd" - integrity sha512-fJ+dNs3CCvEsJK2/Vg5c2ZjuQ860ySOAsodDPwBaVlrGvRN+iCNC8kUfLFL8cT49W4GSiLPa/bHiMjYXA7EhKQ== +"@typescript-eslint/parser@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.10.0.tgz#24b2e48384ab6d5a6121e4c4faf8892c79657ad3" + integrity sha512-wQNiBokcP5ZsTuB+i4BlmVWq6o+oAhd8en2eSm/EE9m7BgZUIfEeYFd6z3S+T7bgNuloeiHA1/cevvbBDLr98g== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.9.0" - "@typescript-eslint/typescript-estree" "2.9.0" + "@typescript-eslint/experimental-utils" "2.10.0" + "@typescript-eslint/typescript-estree" "2.10.0" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -4285,10 +4285,10 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/typescript-estree@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.9.0.tgz#09138daf8f47d0e494ba7db9e77394e928803017" - integrity sha512-v6btSPXEWCP594eZbM+JCXuFoXWXyF/z8kaSBSdCb83DF+Y7+xItW29SsKtSULgLemqJBT+LpT+0ZqdfH7QVmA== +"@typescript-eslint/typescript-estree@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.10.0.tgz#89cdabd5e8c774e9d590588cb42fb9afd14dcbd9" + integrity sha512-oOYnplddQNm/LGVkqbkAwx4TIBuuZ36cAQq9v3nFIU9FmhemHuVzAesMSXNQDdAzCa5bFgCrfD3JWhYVKlRN2g== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" From c7046a080ff4ea4c3d9d0c696c3826b13409e35f Mon Sep 17 00:00:00 2001 From: Matt Bargar Date: Mon, 9 Dec 2019 13:59:49 -0500 Subject: [PATCH 15/36] Flag nested fields as non-aggregatable (#51774) * Flag nested fields as non-aggregatable * Update tests --- .../lib/field_capabilities/field_caps_response.test.js | 7 +++++++ .../fetcher/lib/field_capabilities/field_caps_response.ts | 8 ++++++++ .../index_patterns/fields_for_wildcard_route/response.js | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index 88d2d873521cb0..d9a284d34de6be 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -144,6 +144,13 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } }); }); + it('returns nested sub-fields as non-aggregatable', () => { + const fields = readFieldCapsResponse(esResponse); + // Normally a keyword field would be aggregatable, but the fact that it is nested overrides that + const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); + expect(child).toHaveProperty('aggregatable', false); + }); + it('handles fields that are both nested and multi', () => { const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 06eb30db0b24bb..2215bd8a95a1d0 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -182,6 +182,14 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie if (Object.keys(subType).length > 0) { field.subType = subType; + + // We don't support aggregating on nested fields, trying to do so in the UI will return + // blank results. For now we will stop showing nested fields as an option for aggregation. + // Once we add support for nested fields this condition should be removed and old index + // patterns should be migrated. + if (field.subType.nested) { + field.aggregatable = false; + } } } }); diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index bc70339bd1a665..d72722af06bffb 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -72,7 +72,7 @@ export default function ({ getService }) { readFromDocValues: true, }, { - aggregatable: true, + aggregatable: false, esTypes: [ 'keyword' ], @@ -156,7 +156,7 @@ export default function ({ getService }) { readFromDocValues: true, }, { - aggregatable: true, + aggregatable: false, esTypes: [ 'keyword' ], From 77dca062539413f315538bb2c34673716ec4b789 Mon Sep 17 00:00:00 2001 From: Matt Bargar Date: Mon, 9 Dec 2019 14:16:15 -0500 Subject: [PATCH 16/36] Support nested fields in existing filter types (#49537) * Add automatic support for nested fields in existing filter types * Index pattern could be undefined * add test for handleNestedFilter function * remove console.log * add tests for all "getFilterField" functions * update migrateFilters to work on full filter objects so that it doesn't have to worry about queries that have been wrapped with `nested` * add test to ensure fromFilters auto wraps filters on nested fields * Add smoke test for nested filter and move filter editor tests into their own suite for easier running and debugging * fix bad type change * dedupe filterToQuery logic * fix helper that wasn't doing what it said it did * Convert test from pre-merge to jest * Use new time range style --- .../__fixtures__/index_pattern_response.ts | 322 ++++++++++++++++++ .../es_query/es_query/from_filters.test.ts | 25 ++ .../common/es_query/es_query/from_filters.ts | 27 +- .../es_query/handle_nested_filter.test.ts | 91 +++++ .../es_query/es_query/handle_nested_filter.ts | 45 +++ .../es_query/es_query/migrate_filter.test.ts | 22 +- .../es_query/es_query/migrate_filter.ts | 35 +- .../es_query/filters/exists_filter.test.ts | 37 ++ .../common/es_query/filters/exists_filter.ts | 4 + .../filters/geo_bounding_box_filter.test.ts | 47 +++ .../filters/geo_bounding_box_filter.ts | 7 + .../filters/geo_polygon_filter.test.ts | 45 +++ .../es_query/filters/geo_polygon_filter.ts | 6 + .../es_query/filters/get_filter_field.test.ts | 54 +++ .../es_query/filters/get_filter_field.ts | 53 +++ .../data/common/es_query/filters/index.ts | 1 + .../es_query/filters/missing_filter.test.ts | 39 +++ .../common/es_query/filters/missing_filter.ts | 4 + .../es_query/filters/phrase_filter.test.ts | 21 +- .../es_query/filters/phrases_filter.test.ts | 37 ++ .../common/es_query/filters/phrases_filter.ts | 8 +- .../filters/query_string_filter.test.ts | 2 +- .../es_query/filters/range_filter.test.ts | 17 +- .../common/es_query/filters/range_filter.ts | 4 + test/functional/apps/discover/_discover.js | 15 - .../apps/discover/_filter_editor.js | 73 ++++ test/functional/apps/discover/index.js | 1 + test/functional/services/filter_bar.ts | 1 + 28 files changed, 984 insertions(+), 59 deletions(-) create mode 100644 src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts create mode 100644 src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts create mode 100644 src/plugins/data/common/es_query/es_query/handle_nested_filter.ts create mode 100644 src/plugins/data/common/es_query/filters/exists_filter.test.ts create mode 100644 src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts create mode 100644 src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts create mode 100644 src/plugins/data/common/es_query/filters/get_filter_field.test.ts create mode 100644 src/plugins/data/common/es_query/filters/get_filter_field.ts create mode 100644 src/plugins/data/common/es_query/filters/missing_filter.test.ts create mode 100644 src/plugins/data/common/es_query/filters/phrases_filter.test.ts create mode 100644 test/functional/apps/discover/_filter_editor.js diff --git a/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts b/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts new file mode 100644 index 00000000000000..1784a2650a95a9 --- /dev/null +++ b/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts @@ -0,0 +1,322 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const indexPatternResponse = { + id: 'logstash-*', + title: 'logstash-*', + fields: [ + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'ssl', + type: 'boolean', + esTypes: ['boolean'], + count: 20, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '@timestamp', + type: 'date', + esTypes: ['date'], + count: 30, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'time', + type: 'date', + esTypes: ['date'], + count: 30, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '@tags', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'utc_time', + type: 'date', + esTypes: ['date'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'phpmemory', + type: 'number', + esTypes: ['integer'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'ip', + type: 'ip', + esTypes: ['ip'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'request_body', + type: 'attachment', + esTypes: ['attachment'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'point', + type: 'geo_point', + esTypes: ['geo_point'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'area', + type: 'geo_shape', + esTypes: ['geo_shape'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'hashed', + type: 'murmur3', + esTypes: ['murmur3'], + count: 0, + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + name: 'geo.coordinates', + type: 'geo_point', + esTypes: ['geo_point'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'extension', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'machine.os', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'machine.os.raw', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { multi: { parent: 'machine.os' } }, + }, + { + name: 'geo.src', + type: 'string', + esTypes: ['keyword'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: '_id', + type: 'string', + esTypes: ['_id'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: '_type', + type: 'string', + esTypes: ['_type'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: '_source', + type: '_source', + esTypes: ['_source'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'non-filterable', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: false, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'non-sortable', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + name: 'custom_user_field', + type: 'conflict', + esTypes: ['long', 'text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + name: 'script string', + type: 'string', + count: 0, + scripted: true, + script: "'i am a string'", + lang: 'expression', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'script number', + type: 'number', + count: 0, + scripted: true, + script: '1234', + lang: 'expression', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'script date', + type: 'date', + count: 0, + scripted: true, + script: '1234', + lang: 'painless', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'script murmur3', + type: 'murmur3', + count: 0, + scripted: true, + script: '1234', + lang: 'expression', + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + name: 'nestedField.child', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + subType: { nested: { path: 'nestedField' } }, + }, + { + name: 'nestedField.nestedChild.doublyNestedChild', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + subType: { nested: { path: 'nestedField.nestedChild' } }, + }, + ], +}; diff --git a/src/plugins/data/common/es_query/es_query/from_filters.test.ts b/src/plugins/data/common/es_query/es_query/from_filters.test.ts index 8c1d990c389b8f..a93a91a42dbf38 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.test.ts +++ b/src/plugins/data/common/es_query/es_query/from_filters.test.ts @@ -144,5 +144,30 @@ describe('build query', () => { expect(result.filter).toEqual(expectedESQueries); }); + + test('should wrap filters targeting nested fields in a nested query', () => { + const filters = [ + { + exists: { field: 'nestedField.child' }, + meta: { type: 'exists', alias: '', disabled: false, negate: false }, + }, + ]; + + const expectedESQueries = [ + { + nested: { + path: 'nestedField', + query: { + exists: { + field: 'nestedField.child', + }, + }, + }, + }, + ]; + + const result = buildQueryFromFilters(filters, indexPattern); + expect(result.filter).toEqual(expectedESQueries); + }); }); }); diff --git a/src/plugins/data/common/es_query/es_query/from_filters.ts b/src/plugins/data/common/es_query/es_query/from_filters.ts index e33040485bf47d..ed91d391fc1fda 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.ts +++ b/src/plugins/data/common/es_query/es_query/from_filters.ts @@ -21,6 +21,7 @@ import { migrateFilter } from './migrate_filter'; import { filterMatchesIndex } from './filter_matches_index'; import { Filter, cleanFilter, isFilterDisabled } from '../filters'; import { IIndexPattern } from '../../index_patterns'; +import { handleNestedFilter } from './handle_nested_filter'; /** * Create a filter that can be reversed for filters with negate set @@ -59,20 +60,22 @@ export const buildQueryFromFilters = ( ) => { filters = filters.filter(filter => filter && !isFilterDisabled(filter)); - return { - must: [], - filter: filters - .filter(filterNegate(false)) + const filtersToESQueries = (negate: boolean) => { + return filters + .filter(filterNegate(negate)) .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) + .map(filter => { + return migrateFilter(filter, indexPattern); + }) + .map(filter => handleNestedFilter(filter, indexPattern)) .map(translateToQuery) - .map(cleanFilter) - .map(filter => migrateFilter(filter, indexPattern)), + .map(cleanFilter); + }; + + return { + must: [], + filter: filtersToESQueries(false), should: [], - must_not: filters - .filter(filterNegate(true)) - .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) - .map(translateToQuery) - .map(cleanFilter) - .map(filter => migrateFilter(filter, indexPattern)), + must_not: filtersToESQueries(true), }; }; diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts new file mode 100644 index 00000000000000..594b2641c39be7 --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { handleNestedFilter } from './handle_nested_filter'; +import { fields } from '../../index_patterns/mocks'; +import { buildPhraseFilter, buildQueryFilter } from '../filters'; +import { IFieldType, IIndexPattern } from '../../index_patterns'; + +describe('handleNestedFilter', function() { + const indexPattern: IIndexPattern = ({ + id: 'logstash-*', + fields, + } as unknown) as IIndexPattern; + + it("should return the filter's query wrapped in nested query if the target field is nested", () => { + const field = getField('nestedField.child'); + const filter = buildPhraseFilter(field!, 'foo', indexPattern); + const result = handleNestedFilter(filter, indexPattern); + expect(result).toEqual({ + meta: { + index: 'logstash-*', + }, + nested: { + path: 'nestedField', + query: { + match_phrase: { + 'nestedField.child': 'foo', + }, + }, + }, + }); + }); + + it('should return filter untouched if it does not target a nested field', () => { + const field = getField('extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = handleNestedFilter(filter, indexPattern); + expect(result).toBe(filter); + }); + + it('should return filter untouched if it does not target a field from the given index pattern', () => { + const field = { ...getField('extension'), name: 'notarealfield' }; + const filter = buildPhraseFilter(field as IFieldType, 'jpg', indexPattern); + const result = handleNestedFilter(filter, indexPattern); + expect(result).toBe(filter); + }); + + it('should return filter untouched if no index pattern is provided', () => { + const field = getField('extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = handleNestedFilter(filter); + expect(result).toBe(filter); + }); + + it('should return the filter untouched if a target field cannot be determined', () => { + // for example, we don't support query_string queries + const filter = buildQueryFilter( + { + query: { + query_string: { + query: 'response:200', + }, + }, + }, + 'logstash-*', + 'foo' + ); + const result = handleNestedFilter(filter); + expect(result).toBe(filter); + }); + + function getField(name: string) { + return indexPattern.fields.find(field => field.name === name); + } +}); diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts new file mode 100644 index 00000000000000..27be7925fe00cc --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.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 { getFilterField, cleanFilter, Filter } from '../filters'; +import { IIndexPattern } from '../../index_patterns'; + +export const handleNestedFilter = (filter: Filter, indexPattern?: IIndexPattern) => { + if (!indexPattern) return filter; + + const fieldName = getFilterField(filter); + if (!fieldName) { + return filter; + } + + const field = indexPattern.fields.find(indexPatternField => indexPatternField.name === fieldName); + if (!field || !field.subType || !field.subType.nested || !field.subType.nested.path) { + return filter; + } + + const query = cleanFilter(filter); + + return { + meta: filter.meta, + nested: { + path: field.subType.nested.path, + query: query.query || query, + }, + }; +}; diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts index e01240da87543b..698d7bb48e685d 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts @@ -23,26 +23,32 @@ import { PhraseFilter, MatchAllFilter } from '../filters'; describe('migrateFilter', function() { const oldMatchPhraseFilter = ({ - match: { - fieldFoo: { - query: 'foobar', - type: 'phrase', + query: { + match: { + fieldFoo: { + query: 'foobar', + type: 'phrase', + }, }, }, + meta: {}, } as unknown) as DeprecatedMatchPhraseFilter; const newMatchPhraseFilter = ({ - match_phrase: { - fieldFoo: { - query: 'foobar', + query: { + match_phrase: { + fieldFoo: { + query: 'foobar', + }, }, }, + meta: {}, } as unknown) as PhraseFilter; it('should migrate match filters of type phrase', function() { const migratedFilter = migrateFilter(oldMatchPhraseFilter, undefined); - expect(isEqual(migratedFilter, newMatchPhraseFilter)).toBe(true); + expect(migratedFilter).toEqual(newMatchPhraseFilter); }); it('should not modify the original filter', function() { diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.ts index fdc40768ebe41c..22fbfe0e1ab083 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.ts @@ -22,31 +22,27 @@ import { getConvertedValueForField } from '../filters'; import { Filter } from '../filters'; import { IIndexPattern } from '../../index_patterns'; -/** @deprecated - * see https://github.com/elastic/elasticsearch/pull/17508 - * */ export interface DeprecatedMatchPhraseFilter extends Filter { - match: { - [field: string]: { - query: any; - type: 'phrase'; + query: { + match: { + [field: string]: { + query: any; + type: 'phrase'; + }; }; }; } -/** @deprecated - * see https://github.com/elastic/elasticsearch/pull/17508 - * */ -function isMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter { - const fieldName = filter.match && Object.keys(filter.match)[0]; +function isDeprecatedMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter { + const fieldName = filter.query && filter.query.match && Object.keys(filter.query.match)[0]; - return Boolean(fieldName && get(filter, ['match', fieldName, 'type']) === 'phrase'); + return Boolean(fieldName && get(filter, ['query', 'match', fieldName, 'type']) === 'phrase'); } export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) { - if (isMatchPhraseFilter(filter)) { - const fieldName = Object.keys(filter.match)[0]; - const params: Record = get(filter, ['match', fieldName]); + if (isDeprecatedMatchPhraseFilter(filter)) { + const fieldName = Object.keys(filter.query.match)[0]; + const params: Record = get(filter, ['query', 'match', fieldName]); if (indexPattern) { const field = indexPattern.fields.find(f => f.name === fieldName); @@ -55,8 +51,11 @@ export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) { } } return { - match_phrase: { - [fieldName]: omit(params, 'type'), + ...filter, + query: { + match_phrase: { + [fieldName]: omit(params, 'type'), + }, }, }; } diff --git a/src/plugins/data/common/es_query/filters/exists_filter.test.ts b/src/plugins/data/common/es_query/filters/exists_filter.test.ts new file mode 100644 index 00000000000000..af52192dd85e4a --- /dev/null +++ b/src/plugins/data/common/es_query/filters/exists_filter.test.ts @@ -0,0 +1,37 @@ +/* + * 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 { buildExistsFilter, getExistsFilterField } from './exists_filter'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/fields/fields.mocks.ts'; + +describe('exists filter', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + describe('getExistsFilterField', function() { + it('should return the name of the field an exists query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildExistsFilter(field!, indexPattern); + const result = getExistsFilterField(filter); + expect(result).toBe('extension'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts index a20a4f0634766d..035983dc446dc3 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.ts @@ -33,6 +33,10 @@ export type ExistsFilter = Filter & { export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists; +export const getExistsFilterField = (filter: ExistsFilter) => { + return filter.exists && filter.exists.field; +}; + export const buildExistsFilter = (field: IFieldType, indexPattern: IIndexPattern) => { return { meta: { diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts new file mode 100644 index 00000000000000..63c3a59044c1f2 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { getGeoBoundingBoxFilterField } from './geo_bounding_box_filter'; + +describe('geo_bounding_box filter', function() { + describe('getGeoBoundingBoxFilterField', function() { + it('should return the name of the field a geo_bounding_box query is targeting', () => { + const filter = { + geo_bounding_box: { + geoPointField: { + bottom_right: { lat: 1, lon: 1 }, + top_left: { lat: 1, lon: 1 }, + }, + ignore_unmapped: true, + }, + meta: { + disabled: false, + negate: false, + alias: null, + params: { + bottom_right: { lat: 1, lon: 1 }, + top_left: { lat: 1, lon: 1 }, + }, + }, + }; + const result = getGeoBoundingBoxFilterField(filter); + expect(result).toBe('geoPointField'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts index f4673af96b2cd6..619903954ff551 100644 --- a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts +++ b/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts @@ -33,3 +33,10 @@ export type GeoBoundingBoxFilter = Filter & { export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFilter => filter && filter.geo_bounding_box; + +export const getGeoBoundingBoxFilterField = (filter: GeoBoundingBoxFilter) => { + return ( + filter.geo_bounding_box && + Object.keys(filter.geo_bounding_box).find(key => key !== 'ignore_unmapped') + ); +}; diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts new file mode 100644 index 00000000000000..ba8e43b0cea856 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/geo_polygon_filter.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 { getGeoPolygonFilterField } from './geo_polygon_filter'; + +describe('geo_polygon filter', function() { + describe('getGeoPolygonFilterField', function() { + it('should return the name of the field a geo_polygon query is targeting', () => { + const filter = { + geo_polygon: { + geoPointField: { + points: [{ lat: 1, lon: 1 }], + }, + ignore_unmapped: true, + }, + meta: { + disabled: false, + negate: false, + alias: null, + params: { + points: [{ lat: 1, lon: 1 }], + }, + }, + }; + const result = getGeoPolygonFilterField(filter); + expect(result).toBe('geoPointField'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts index 4cf82a92d2cef4..03367feb83ee4e 100644 --- a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts +++ b/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts @@ -32,3 +32,9 @@ export type GeoPolygonFilter = Filter & { export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter => filter && filter.geo_polygon; + +export const getGeoPolygonFilterField = (filter: GeoPolygonFilter) => { + return ( + filter.geo_polygon && Object.keys(filter.geo_polygon).find(key => key !== 'ignore_unmapped') + ); +}; diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.test.ts b/src/plugins/data/common/es_query/filters/get_filter_field.test.ts new file mode 100644 index 00000000000000..2fc8ffef9713b5 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/get_filter_field.test.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { buildPhraseFilter } from './phrase_filter'; +import { buildQueryFilter } from './query_string_filter'; +import { getFilterField } from './get_filter_field'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/fields/fields.mocks.ts'; + +describe('getFilterField', function() { + const indexPattern: IIndexPattern = ({ + id: 'logstash-*', + fields, + } as unknown) as IIndexPattern; + + it('should return the field name from known filter types that target a specific field', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = getFilterField(filter); + expect(result).toBe('extension'); + }); + + it('should return undefined for filters that do not target a specific field', () => { + const filter = buildQueryFilter( + { + query: { + query_string: { + query: 'response:200 and extension:jpg', + }, + }, + }, + indexPattern.id!, + '' + ); + const result = getFilterField(filter); + expect(result).toBe(undefined); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.ts b/src/plugins/data/common/es_query/filters/get_filter_field.ts new file mode 100644 index 00000000000000..dfb575157d3620 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/get_filter_field.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Filter } from './meta_filter'; +import { getExistsFilterField, isExistsFilter } from './exists_filter'; +import { getGeoBoundingBoxFilterField, isGeoBoundingBoxFilter } from './geo_bounding_box_filter'; +import { getGeoPolygonFilterField, isGeoPolygonFilter } from './geo_polygon_filter'; +import { getPhraseFilterField, isPhraseFilter } from './phrase_filter'; +import { getPhrasesFilterField, isPhrasesFilter } from './phrases_filter'; +import { getRangeFilterField, isRangeFilter } from './range_filter'; +import { getMissingFilterField, isMissingFilter } from './missing_filter'; + +export const getFilterField = (filter: Filter) => { + if (isExistsFilter(filter)) { + return getExistsFilterField(filter); + } + if (isGeoBoundingBoxFilter(filter)) { + return getGeoBoundingBoxFilterField(filter); + } + if (isGeoPolygonFilter(filter)) { + return getGeoPolygonFilterField(filter); + } + if (isPhraseFilter(filter)) { + return getPhraseFilterField(filter); + } + if (isPhrasesFilter(filter)) { + return getPhrasesFilterField(filter); + } + if (isRangeFilter(filter)) { + return getRangeFilterField(filter); + } + if (isMissingFilter(filter)) { + return getMissingFilterField(filter); + } + + return; +}; diff --git a/src/plugins/data/common/es_query/filters/index.ts b/src/plugins/data/common/es_query/filters/index.ts index 1bd534bf74ff7c..403ff2b79b55f9 100644 --- a/src/plugins/data/common/es_query/filters/index.ts +++ b/src/plugins/data/common/es_query/filters/index.ts @@ -22,6 +22,7 @@ import { Filter } from './meta_filter'; export * from './build_filters'; export * from './get_filter_params'; +export * from './get_filter_field'; export * from './custom_filter'; export * from './exists_filter'; diff --git a/src/plugins/data/common/es_query/filters/missing_filter.test.ts b/src/plugins/data/common/es_query/filters/missing_filter.test.ts new file mode 100644 index 00000000000000..240d8fb26f3e0a --- /dev/null +++ b/src/plugins/data/common/es_query/filters/missing_filter.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { getMissingFilterField } from './missing_filter'; + +describe('missing filter', function() { + describe('getMissingFilterField', function() { + it('should return the name of the field an missing query is targeting', () => { + const filter = { + missing: { + field: 'extension', + }, + meta: { + disabled: false, + negate: false, + alias: null, + }, + }; + const result = getMissingFilterField(filter); + expect(result).toBe('extension'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/missing_filter.ts b/src/plugins/data/common/es_query/filters/missing_filter.ts index 5411187cbcfd73..c8e1194a8f3cc4 100644 --- a/src/plugins/data/common/es_query/filters/missing_filter.ts +++ b/src/plugins/data/common/es_query/filters/missing_filter.ts @@ -27,3 +27,7 @@ export type MissingFilter = Filter & { }; export const isMissingFilter = (filter: any): filter is MissingFilter => filter && filter.missing; + +export const getMissingFilterField = (filter: MissingFilter) => { + return filter.missing && filter.missing.field; +}; diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts index 3c7d00a80fecf8..9f90097e55475c 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts @@ -17,8 +17,12 @@ * under the License. */ -import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter'; -import { getField } from '../../index_patterns/mocks'; +import { + buildInlineScriptForPhraseFilter, + buildPhraseFilter, + getPhraseFilterField, +} from './phrase_filter'; +import { fields, getField } from '../../index_patterns/mocks'; import { IIndexPattern } from '../../index_patterns'; describe('Phrase filter builder', () => { @@ -95,3 +99,16 @@ describe('buildInlineScriptForPhraseFilter', () => { expect(buildInlineScriptForPhraseFilter(field)).toBe(expected); }); }); + +describe('getPhraseFilterField', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + it('should return the name of the field a phrase query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildPhraseFilter(field!, 'jpg', indexPattern); + const result = getPhraseFilterField(filter); + expect(result).toBe('extension'); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.test.ts b/src/plugins/data/common/es_query/filters/phrases_filter.test.ts new file mode 100644 index 00000000000000..3a121eb9da034a --- /dev/null +++ b/src/plugins/data/common/es_query/filters/phrases_filter.test.ts @@ -0,0 +1,37 @@ +/* + * 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 { buildPhrasesFilter, getPhrasesFilterField } from './phrases_filter'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/fields/fields.mocks.ts'; + +describe('phrases filter', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + describe('getPhrasesFilterField', function() { + it('should return the name of the field a phrases query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'extension'); + const filter = buildPhrasesFilter(field!, ['jpg', 'png'], indexPattern); + const result = getPhrasesFilterField(filter); + expect(result).toBe('extension'); + }); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts index f7164f0ad3c836..006e0623be9139 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -32,7 +32,13 @@ export type PhrasesFilter = Filter & { }; export const isPhrasesFilter = (filter: any): filter is PhrasesFilter => - filter && filter.meta.type === FILTERS.PHRASES; + filter?.meta?.type === FILTERS.PHRASES; + +export const getPhrasesFilterField = (filter: PhrasesFilter) => { + // Phrases is a newer filter type that has always been created via a constructor that ensures + // `meta.key` is set to the field name + return filter.meta.key; +}; // Creates a filter where the given field matches one or more of the given values // params should be an array of values diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts index 4fcb15ccac44a4..18285194c60548 100644 --- a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts @@ -19,7 +19,7 @@ import { buildQueryFilter } from './query_string_filter'; -describe('Phrase filter builder', () => { +describe('Query string filter builder', () => { it('should be a function', () => { expect(typeof buildQueryFilter).toBe('function'); }); diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts index 56b63018b51533..45d59c97941b3c 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -18,8 +18,8 @@ */ import { each } from 'lodash'; -import { buildRangeFilter, RangeFilter } from './range_filter'; -import { getField } from '../../index_patterns/mocks'; +import { buildRangeFilter, getRangeFilterField, RangeFilter } from './range_filter'; +import { fields, getField } from '../../index_patterns/mocks'; import { IIndexPattern, IFieldType } from '../../index_patterns'; describe('Range filter builder', () => { @@ -172,3 +172,16 @@ describe('Range filter builder', () => { }); }); }); + +describe('getRangeFilterField', function() { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + test('should return the name of the field a range query is targeting', () => { + const field = indexPattern.fields.find(patternField => patternField.name === 'bytes'); + const filter = buildRangeFilter(field!, {}, indexPattern); + const result = getRangeFilterField(filter); + expect(result).toBe('bytes'); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index 3d819bd145fa63..b300539f4280a5 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -88,6 +88,10 @@ export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => { return hasRangeKeys(params); }; +export const getRangeFilterField = (filter: RangeFilter) => { + return filter.range && Object.keys(filter.range)[0]; +}; + const formatValue = (field: IFieldType, params: any[]) => map(params, (val: any, key: string) => get(operators, key) + format(field, val)).join(' '); diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 94b2941ecd3d18..58eef9fbf6b877 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -25,7 +25,6 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - const filterBar = getService('filterBar'); const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { @@ -173,20 +172,6 @@ export default function ({ getService, getPageObjects }) { }); - describe('filter editor', function () { - it('should add a phrases filter', async function () { - await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); - expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); - }); - - it('should show the phrases if you re-open a phrases filter', async function () { - await filterBar.clickEditFilter('extension.raw', 'jpg'); - const phrases = await filterBar.getFilterEditorSelectedPhrases(); - expect(phrases.length).to.be(1); - expect(phrases[0]).to.be('jpg'); - }); - }); - describe('data-shared-item', function () { it('should have correct data-shared-item title and description', async () => { const expected = { diff --git a/test/functional/apps/discover/_filter_editor.js b/test/functional/apps/discover/_filter_editor.js new file mode 100644 index 00000000000000..cab16252add4a6 --- /dev/null +++ b/test/functional/apps/discover/_filter_editor.js @@ -0,0 +1,73 @@ +/* + * 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 expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }) { + const log = getService('log'); + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const filterBar = getService('filterBar'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); + const defaultSettings = { + defaultIndex: 'logstash-*', + }; + + describe('discover filter editor', function describeIndexTests() { + + before(async function () { + log.debug('load kibana index with default index pattern'); + await esArchiver.loadIfNeeded('discover'); + + // and load a set of makelogs data + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + log.debug('discover filter editor'); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + }); + + describe('filter editor', function () { + it('should add a phrases filter', async function () { + await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); + expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); + }); + + it('should show the phrases if you re-open a phrases filter', async function () { + await filterBar.clickEditFilter('extension.raw', 'jpg'); + const phrases = await filterBar.getFilterEditorSelectedPhrases(); + expect(phrases.length).to.be(1); + expect(phrases[0]).to.be('jpg'); + await filterBar.ensureFieldEditorModalIsClosed(); + }); + + it('should support filtering on nested fields', async () => { + await filterBar.addFilter('nestedField.child', 'is', 'nestedValue'); + expect(await filterBar.hasFilter('nestedField.child', 'nestedValue')).to.be(true); + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be( + '1' + ); + }); + }); + }); + }); +} diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index 902490bebd1acd..28df897b67c099 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -34,6 +34,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_saved_queries')); loadTestFile(require.resolve('./_discover')); + loadTestFile(require.resolve('./_filter_editor')); loadTestFile(require.resolve('./_errors')); loadTestFile(require.resolve('./_field_data')); loadTestFile(require.resolve('./_shared_links')); diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index adf0f2266ba178..9d494b1e6d950c 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -166,6 +166,7 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon if (cancelSaveFilterModalButtonExists) { await testSubjects.click('cancelSaveFilter'); } + await testSubjects.waitForDeleted('cancelSaveFilter'); } /** From 4bbe3cf85b334694cd2af035d2f4d75fb6e5a417 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 9 Dec 2019 12:39:06 -0700 Subject: [PATCH 17/36] [SIEM][Detection Engine] Removes filter type, fixes bugs, adds more examples (#52452) ## Summary * This removes the filter type and all the tests associated with it. * This fixes a critical bug where filter from params was being passed down instead of esFilter * This adds and cleans up all the rule examples for documenters and developers and users. * This fixes a bug with empty queries * This makes it so you can have simple filters which replace the filter capability * This cleans up info and debug messages more ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ - [x] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../server/lib/detection_engine/README.md | 5 +- .../alerts/__mocks__/es_results.ts | 1 - .../detection_engine/alerts/create_rules.ts | 2 - .../alerts/get_filter.test.ts | 23 -- .../lib/detection_engine/alerts/get_filter.ts | 5 - .../alerts/rules_alert_type.ts | 24 +- .../lib/detection_engine/alerts/types.ts | 3 +- .../detection_engine/alerts/update_rules.ts | 2 - .../lib/detection_engine/alerts/utils.test.ts | 11 + .../lib/detection_engine/alerts/utils.ts | 12 +- .../routes/__mocks__/request_responses.ts | 14 - .../routes/create_rules_route.test.ts | 21 -- .../routes/create_rules_route.ts | 4 +- .../routes/index/create_index_route.ts | 2 +- .../routes/index/delete_index_route.ts | 2 +- .../routes/index/read_index_route.ts | 2 +- .../detection_engine/routes/schemas.test.ts | 290 +----------------- .../lib/detection_engine/routes/schemas.ts | 78 +---- .../routes/update_rules_route.test.ts | 15 - .../routes/update_rules_route.ts | 2 - .../lib/detection_engine/routes/utils.test.ts | 8 +- .../lib/detection_engine/routes/utils.ts | 5 +- .../lib/detection_engine/scripts/post_rule.sh | 6 +- .../scripts/rules/queries/README.md | 21 ++ .../scripts/rules/queries/query_disabled.json | 11 + .../rules/queries/query_immutable.json | 11 + .../scripts/rules/queries/query_lucene.json | 11 + .../query_mitre_attack.json} | 13 +- .../rules/queries/query_with_everything.json | 80 +++++ .../query_with_filter.json} | 10 +- .../rules/queries/query_with_meta_data.json | 17 + .../rules/queries/query_with_rule_id.json | 11 + .../rules/queries/simplest_filters.json | 18 ++ .../scripts/rules/queries/simplest_query.json | 10 + .../scripts/rules/root_or_admin_1.json | 14 - .../scripts/rules/root_or_admin_10.json | 12 - .../scripts/rules/root_or_admin_2.json | 13 - .../scripts/rules/root_or_admin_3.json | 13 - .../scripts/rules/root_or_admin_4.json | 14 - .../scripts/rules/root_or_admin_8.json | 15 - .../scripts/rules/root_or_admin_9.json | 18 -- .../rules/root_or_admin_filter_9998.json | 46 --- .../rules/root_or_admin_filter_9999.json | 42 --- .../scripts/rules/root_or_admin_meta.json | 19 -- .../rules/root_or_admin_saved_query_1.json | 12 - .../rules/root_or_admin_saved_query_2.json | 14 - .../rules/root_or_admin_saved_query_3.json | 12 - .../scripts/rules/root_or_admin_update_1.json | 14 - .../scripts/rules/root_or_admin_update_2.json | 17 - .../scripts/rules/saved_queries/README.md | 32 ++ .../saved_queries/saved_query_by_rule_id.json | 11 + .../saved_query_with_everything.json | 81 +++++ .../saved_query_with_filters.json | 19 ++ .../saved_queries/saved_query_with_query.json | 11 + .../saved_query_with_query_filter.json | 26 ++ .../saved_queries/simplest_saved_query.json | 10 + .../scripts/rules/test_cases/README.md | 18 ++ .../filter_with_empty_query.json | 0 .../filter_without_query.json | 0 .../query_filter_ui_meatadata_lucene.json} | 6 +- .../query_filter_ui_metadata.json} | 6 +- .../saved_query_ui_meta_empty_query.json | 36 +++ .../scripts/rules/updates/README.md | 25 ++ .../scripts/rules/updates/disable_rule.json | 4 + .../scripts/rules/updates/enabled_rule.json | 4 + .../simplest_update_risk_score_by_id.json | 4 + ...simplest_update_risk_score_by_rule_id.json | 4 + .../rules/updates/simplest_updated_name.json | 4 + .../updates/update_query_everything.json | 80 +++++ .../scripts/rules/watch_longmont.json | 13 - .../detection_engine/scripts/update_rule.sh | 6 +- 71 files changed, 642 insertions(+), 783 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/{root_or_admin_threats.json => queries/query_mitre_attack.json} (74%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/{root_or_admin_5.json => queries/query_with_filter.json} (62%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/{ => test_cases}/filter_with_empty_query.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/{ => test_cases}/filter_without_query.json (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/{root_or_admin_7.json => test_cases/query_filter_ui_meatadata_lucene.json} (80%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/{root_or_admin_6.json => test_cases/query_filter_ui_metadata.json} (82%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md index d82424c1c3bd3a..7c22d6334a2d13 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md @@ -60,7 +60,7 @@ which will: - Delete any existing alert tasks you have - Delete any existing signal mapping, policies, and template, you might have previously had. - Add the latest signal index and its mappings using your settings from `kibana.dev.yml` environment variable of `xpack.siem.signalsIndex`. -- Posts the sample rule from `rules/root_or_admin_1.json` +- Posts the sample rule from `./rules/queries/query_with_rule_id.json` - The sample rule checks for root or admin every 5 minutes and reports that as a signal if it is a positive hit Now you can run @@ -154,11 +154,12 @@ logging.events: See these two README.md's pages for more references on the alerting and actions API: https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/alerting/README.md https://github.com/elastic/kibana/tree/master/x-pack/legacy/plugins/actions + ### Signals API To update the status of a signal or group of signals, the following scripts provide an example of how to go about doing so. -` cd x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts` +`cd x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts` `./signals/put_signal_doc.sh` will post a sample signal doc into the signals index to play with `./signals/set_status_with_id.sh closed` will update the status of the sample signal to closed `./signals/set_status_with_id.sh open` will update the status of the sample signal to open diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts index bed466dd9b94f1..435a8d9bf86881 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts @@ -31,7 +31,6 @@ export const sampleRuleAlertParams = ( references: ['http://google.com'], riskScore: riskScore ? riskScore : 50, maxSignals: maxSignals ? maxSignals : 10000, - filter: undefined, filters: undefined, savedId: undefined, meta: undefined, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts index 4418bbc52b57d6..e673bb116e1dd9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts @@ -13,7 +13,6 @@ export const createRules = async ({ description, enabled, falsePositives, - filter, from, query, language, @@ -46,7 +45,6 @@ export const createRules = async ({ index, falsePositives, from, - filter, immutable, query, language, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts index c55c99fb291c44..e1d10e2efdefb3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts @@ -385,26 +385,9 @@ describe('get_filter', () => { }); describe('getFilter', () => { - test('returns a filter if given a type of filter as is', async () => { - const filter = await getFilter({ - type: 'filter', - filter: { something: '1' }, - filters: undefined, - language: undefined, - query: undefined, - savedId: undefined, - services: servicesMock, - index: ['auditbeat-*'], - }); - expect(filter).toEqual({ - something: '1', - }); - }); - test('returns a query if given a type of query', async () => { const filter = await getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: 'host.name: siem', @@ -439,7 +422,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: undefined, query: 'host.name: siem', @@ -454,7 +436,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: undefined, @@ -469,7 +450,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: 'host.name: siem', @@ -483,7 +463,6 @@ describe('get_filter', () => { test('returns a saved query if given a type of query', async () => { const filter = await getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, @@ -507,7 +486,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, @@ -522,7 +500,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts index 5d3b47ecebfd51..858f3580f57e8c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts @@ -42,7 +42,6 @@ export const getQueryFilter = ( interface GetFilterArgs { type: RuleAlertParams['type']; - filter: Record | undefined | null; filters: PartialFilter[] | undefined | null; language: string | undefined | null; query: string | undefined | null; @@ -52,7 +51,6 @@ interface GetFilterArgs { } export const getFilter = async ({ - filter, filters, index, language, @@ -95,9 +93,6 @@ export const getFilter = async ({ throw new TypeError('savedId parameter should be defined'); } } - case 'filter': { - return filter; - } } return assertUnreachable(type); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts index 577de2ce0d5323..488c34c945b484 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts @@ -34,7 +34,6 @@ export const rulesAlertType = ({ description: schema.string(), falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }), from: schema.string(), - filter: schema.nullable(schema.object({}, { allowUnknowns: true })), ruleId: schema.string(), immutable: schema.boolean({ defaultValue: false }), index: schema.nullable(schema.arrayOf(schema.string())), @@ -56,7 +55,6 @@ export const rulesAlertType = ({ }, async executor({ alertId, services, params }) { const { - filter, from, ruleId, index, @@ -87,7 +85,6 @@ export const rulesAlertType = ({ const inputIndex = await getInputIndex(services, version, index); const esFilter = await getFilter({ type, - filter, filters, language, query, @@ -106,18 +103,20 @@ export const rulesAlertType = ({ }); try { - logger.debug(`Starting signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); logger.debug( - `[+] Initial search call of signal rule "id: ${alertId}", "ruleId: ${ruleId}"` + `Starting signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); + logger.debug( + `[+] Initial search call of signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` ); const noReIndexResult = await services.callCluster('search', noReIndex); if (noReIndexResult.hits.total.value !== 0) { logger.info( `Found ${ noReIndexResult.hits.total.value - } signals from the indexes of "${inputIndex.join( + } signals from the indexes of "[${inputIndex.join( ', ' - )}" using signal rule "id: ${alertId}", "ruleId: ${ruleId}", pushing signals to index ${outputIndex}` + )}]" using signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}", pushing signals to index "${outputIndex}"` ); } @@ -128,6 +127,7 @@ export const rulesAlertType = ({ logger, id: alertId, signalsIndex: outputIndex, + filter: esFilter, name, createdBy, updatedBy, @@ -137,15 +137,19 @@ export const rulesAlertType = ({ }); if (bulkIndexResult) { - logger.debug(`Finished signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); + logger.debug( + `Finished signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); } else { - logger.error(`Error processing signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); + logger.error( + `Error processing signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); } } catch (err) { // TODO: Error handling and writing of errors into a signal that has error // handling/conditions logger.error( - `Error from signal rule "id: ${alertId}", "ruleId: ${ruleId}", ${err.message}` + `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` ); } }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index cb1d59254923b2..7f61765f5532cc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -35,7 +35,6 @@ export interface RuleAlertParams { description: string; enabled: boolean; falsePositives: string[]; - filter: Record | undefined | null; filters: PartialFilter[] | undefined | null; from: string; immutable: boolean; @@ -55,7 +54,7 @@ export interface RuleAlertParams { tags: string[]; to: string; threats: ThreatParams[] | undefined | null; - type: 'filter' | 'query' | 'saved_query'; + type: 'query' | 'saved_query'; } export type RuleAlertParamsRest = Omit< diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts index d6b828642433de..a2d49b88a8f643 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts @@ -53,7 +53,6 @@ export const updateRules = async ({ savedId, meta, filters, - filter, from, immutable, id, @@ -88,7 +87,6 @@ export const updateRules = async ({ { description, falsePositives, - filter, from, immutable, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts index fc50e54e06e4e1..83bf509fa7a937 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts @@ -530,6 +530,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }) ).rejects.toThrow('Attempted to search after with empty sort id'); }); @@ -543,6 +544,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }); expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); }); @@ -559,6 +561,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }) ).rejects.toThrow('Fake Error'); }); @@ -579,6 +582,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(result).toEqual(true); @@ -629,6 +633,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(result).toEqual(true); @@ -650,6 +655,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -678,6 +684,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -706,6 +713,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(result).toEqual(true); }); @@ -736,6 +744,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(result).toEqual(true); }); @@ -766,6 +775,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(result).toEqual(true); }); @@ -798,6 +808,7 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, }); expect(result).toEqual(false); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index 9dedda6d79839e..a7668f47b614c4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -47,7 +47,6 @@ export const buildRule = ({ risk_score: ruleParams.riskScore, output_index: ruleParams.outputIndex, description: ruleParams.description, - filter: ruleParams.filter, from: ruleParams.from, immutable: ruleParams.immutable, index: ruleParams.index, @@ -207,7 +206,9 @@ export const singleBulkCreate = async ({ body: bulkBody, }); const time2 = performance.now(); - logger.debug(`individual bulk process time took: ${time2 - time1} milliseconds`); + logger.debug( + `individual bulk process time took: ${Number(time2 - time1).toFixed(2)} milliseconds` + ); logger.debug(`took property says bulk took: ${firstResult.took} milliseconds`); if (firstResult.errors) { // go through the response status errors and see what @@ -241,6 +242,7 @@ interface SingleSearchAfterParams { services: AlertServices; logger: Logger; pageSize: number; + filter: unknown; } // utilize search_after for paging results into bulk. @@ -248,6 +250,7 @@ export const singleSearchAfter = async ({ searchAfterSortId, ruleParams, services, + filter, logger, pageSize, }: SingleSearchAfterParams): Promise => { @@ -259,7 +262,7 @@ export const singleSearchAfter = async ({ index: ruleParams.index, from: ruleParams.from, to: ruleParams.to, - filter: ruleParams.filter, + filter, size: pageSize, searchAfterSortId, }); @@ -287,6 +290,7 @@ interface SearchAfterAndBulkCreateParams { interval: string; enabled: boolean; pageSize: number; + filter: unknown; } // search_after through documents and re-index using bulk endpoint. @@ -297,6 +301,7 @@ export const searchAfterAndBulkCreate = async ({ logger, id, signalsIndex, + filter, name, createdBy, updatedBy, @@ -353,6 +358,7 @@ export const searchAfterAndBulkCreate = async ({ ruleParams, services, logger, + filter, pageSize, // maximum number of docs to receive per search result. }); if (searchAfterResult.hits.hits.length === 0) { 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 c18891c0320607..cd8b716221b9b8 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 @@ -38,20 +38,6 @@ export const typicalPayload = (): Partial> = ], }); -export const typicalFilterPayload = (): Partial => ({ - rule_id: 'rule-1', - description: 'Detecting root and admin users', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - name: 'Detect Root/Admin Users', - risk_score: 50, - type: 'filter', - from: 'now-6m', - to: 'now', - severity: 'high', - filter: {}, -}); - export const typicalSetStatusSignalByIdsPayload = (): Partial => ({ signal_ids: ['somefakeid1', 'somefakeid2'], status: 'closed', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts index 4aa57f005445b2..b271af2db1e7dd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts @@ -103,27 +103,6 @@ describe('create_rules', () => { expect(statusCode).toBe(200); }); - test('returns 200 if type is filter', async () => { - alertsClient.find.mockResolvedValue(getFindResult()); - alertsClient.get.mockResolvedValue(getResult()); - actionsClient.create.mockResolvedValue(createActionResult()); - alertsClient.create.mockResolvedValue(getResult()); - // Cannot type request with a ServerInjectOptions as the type system complains - // about the property filter involving Hapi types, so I left it off for now - const { language, query, type, ...noType } = typicalPayload(); - const request = { - method: 'POST', - url: DETECTION_ENGINE_RULES_URL, - payload: { - ...noType, - type: 'filter', - filter: {}, - }, - }; - const { statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - }); - test('returns 400 if type is not filter or kql', async () => { alertsClient.find.mockResolvedValue(getFindResult()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts index 31068dac5d23a6..e14c889934537b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts @@ -35,7 +35,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = description, enabled, false_positives: falsePositives, - filter, from, immutable, query, @@ -81,7 +80,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = if (ruleId != null) { const rule = await readRules({ alertsClient, ruleId }); if (rule != null) { - return new Boom(`rule_id ${ruleId} already exists`, { statusCode: 409 }); + return new Boom(`rule_id: "${ruleId}" already exists`, { statusCode: 409 }); } } const createdRule = await createRules({ @@ -90,7 +89,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = description, enabled, falsePositives, - filter, from, immutable, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts index 14a061fd4ccb79..619c2eccf3d99f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts @@ -37,7 +37,7 @@ export const createCreateIndexRoute = (server: ServerFacade): Hapi.ServerRoute = const callWithRequest = callWithRequestFactory(request); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { - return new Boom(`index ${index} already exists`, { statusCode: 409 }); + return new Boom(`index: "${index}" already exists`, { statusCode: 409 }); } else { const policyExists = await getPolicyExists(callWithRequest, index); if (!policyExists) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts index 89f21bbada939f..ca2905320d5b64 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -45,7 +45,7 @@ export const createDeleteIndexRoute = (server: ServerFacade): Hapi.ServerRoute = const callWithRequest = callWithRequestFactory(request); const indexExists = await getIndexExists(callWithRequest, index); if (!indexExists) { - return new Boom(`index ${index} does not exist`, { statusCode: 404 }); + return new Boom(`index: "${index}" does not exist`, { statusCode: 404 }); } else { await deleteAllIndex(callWithRequest, `${index}-*`); const policyExists = await getPolicyExists(callWithRequest, index); 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 87b6ffb985c371..9d5aa07a330204 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 @@ -42,7 +42,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute => if (request.method.toLowerCase() === 'head') { return headers.response().code(404); } else { - return new Boom('An index for this space does not exist', { statusCode: 404 }); + return new Boom('index for this space does not exist', { statusCode: 404 }); } } } catch (err) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts index dd45490c778bff..f5147bc5a8f8b0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts @@ -141,7 +141,7 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does not validate', () => { + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { expect( createRulesSchema.validate>({ rule_id: 'rule-1', @@ -156,7 +156,7 @@ describe('schemas', () => { index: ['index-1'], interval: '5m', }).error - ).toBeTruthy(); + ).toBeFalsy(); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -227,8 +227,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', risk_score: 50, }).error ).toBeFalsy(); @@ -247,8 +246,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', }).error ).toBeFalsy(); }); @@ -287,8 +285,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', threats: [ { framework: 'someFramework', @@ -310,84 +307,6 @@ describe('schemas', () => { ).toBeFalsy(); }); - test('If filter type is set then filter is required', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then query is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('allows references to be sent as valid', () => { expect( createRulesSchema.validate>({ @@ -509,26 +428,6 @@ describe('schemas', () => { ).toEqual(100); }); - test('filter and filters cannot exist together', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('saved_id is required when type is saved_query and will not validate without out', () => { expect( createRulesSchema.validate>({ @@ -608,26 +507,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('saved_query type cannot have filter with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - test('language validates with kuery', () => { expect( createRulesSchema.validate>({ @@ -1157,30 +1036,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('You can have an empty query string when filters are present', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: '', - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - test('You can omit the query string when filters are present', () => { expect( createRulesSchema.validate & { meta: string }>>({ @@ -1203,29 +1058,6 @@ describe('schemas', () => { }).error ).toBeFalsy(); }); - - test('query string defaults to empty string when present with filters', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - language: 'kuery', - filters: [], - max_signals: 1, - }).value.query - ).toEqual(''); - }); }); describe('update rules schema', () => { @@ -1556,8 +1388,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', }).error ).toBeFalsy(); }); @@ -1573,82 +1404,11 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, - }).error - ).toBeFalsy(); - }); - - test('If filter type is set then filter is still not required', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', + type: 'query', }).error ).toBeFalsy(); }); - test('If filter type is set then query is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('allows references to be sent as a valid value to update with', () => { expect( updateRulesSchema.validate>({ @@ -1758,24 +1518,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('filter and filters cannot exist together', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('saved_id is not required when type is saved_query and will validate without it', () => { expect( updateRulesSchema.validate>({ @@ -1827,24 +1569,6 @@ describe('schemas', () => { ).toBeFalsy(); }); - test('saved_query type cannot have filter with it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - test('language validates with kuery', () => { expect( updateRulesSchema.validate>({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts index d1a7a01c724156..6ed6fdd2577d8a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -11,7 +11,6 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; const description = Joi.string(); const enabled = Joi.boolean(); const false_positives = Joi.array().items(Joi.string()); -const filter = Joi.object(); const filters = Joi.array(); const from = Joi.string(); const immutable = Joi.boolean(); @@ -34,7 +33,7 @@ const risk_score = Joi.number() const severity = Joi.string(); const status = Joi.string().valid('open', 'closed'); const to = Joi.string(); -const type = Joi.string().valid('filter', 'query', 'saved_query'); +const type = Joi.string().valid('query', 'saved_query'); const queryFilter = Joi.string(); const references = Joi.array() .items(Joi.string()) @@ -85,46 +84,14 @@ export const createRulesSchema = Joi.object({ description: description.required(), enabled: enabled.default(true), false_positives: false_positives.default([]), - filter: filter.when('type', { is: 'filter', then: Joi.required(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), + filters, from: from.required(), rule_id, immutable: immutable.default(false), index, interval: interval.default('5m'), - query: Joi.when('type', { - is: 'query', - then: Joi.when('filters', { - is: Joi.exist(), - then: query - .optional() - .allow('') - .default(''), - otherwise: Joi.required(), - }), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.required(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), + query: query.allow('').default(''), + language: language.default('kuery'), output_index, saved_id: saved_id.when('type', { is: 'saved_query', @@ -147,46 +114,17 @@ export const updateRulesSchema = Joi.object({ description, enabled, false_positives, - filter: filter.when('type', { is: 'filter', then: Joi.optional(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), + filters, from, rule_id, id, immutable, index, interval, - query: Joi.when('type', { - is: 'query', - then: query.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), + query: query.allow(''), + language, output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.optional(), - otherwise: Joi.forbidden(), - }), + saved_id, meta, risk_score, max_signals, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts index d03d68417dd5d5..dfa1275a6b26b9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts @@ -20,7 +20,6 @@ import { getUpdateRequest, typicalPayload, getFindResultWithSingleHit, - typicalFilterPayload, } from './__mocks__/request_responses'; import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; @@ -119,20 +118,6 @@ describe('update_rules', () => { expect(statusCode).toBe(200); }); - test('returns 200 if type is filter', async () => { - alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); - alertsClient.get.mockResolvedValue(getResult()); - actionsClient.update.mockResolvedValue(updateActionResult()); - alertsClient.update.mockResolvedValue(getResult()); - const request: ServerInjectOptions = { - method: 'PUT', - url: DETECTION_ENGINE_RULES_URL, - payload: typicalFilterPayload(), - }; - const { statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - }); - test('returns 400 if type is not filter or kql', async () => { alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts index 156756698435fe..943c41fd6dea68 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts @@ -30,7 +30,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { description, enabled, false_positives: falsePositives, - filter, from, immutable, query, @@ -68,7 +67,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { description, enabled, falsePositives, - filter, from, immutable, query, 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 d20b4d213e9cc6..4663ea357f259e 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 @@ -341,22 +341,22 @@ describe('utils', () => { describe('getIdError', () => { test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { const boom = getIdError({ id: '123', ruleId: undefined }); - expect(boom.message).toEqual('id of 123 not found'); + expect(boom.message).toEqual('id: "123" not found'); }); test('outputs message about id not being found if only id is defined and ruleId is null', () => { const boom = getIdError({ id: '123', ruleId: null }); - expect(boom.message).toEqual('id of 123 not found'); + expect(boom.message).toEqual('id: "123" not found'); }); test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { const boom = getIdError({ id: undefined, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id of rule-id-123 not found'); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); }); test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { const boom = getIdError({ id: null, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id of rule-id-123 not found'); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); }); test('outputs message about both being not defined when both are undefined', () => { 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 4c5a5a6af93de3..88f072f38b7e1d 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 @@ -18,9 +18,9 @@ export const getIdError = ({ ruleId: string | undefined | null; }) => { if (id != null) { - return new Boom(`id of ${id} not found`, { statusCode: 404 }); + return new Boom(`id: "${id}" not found`, { statusCode: 404 }); } else if (ruleId != null) { - return new Boom(`rule_id of ${ruleId} not found`, { statusCode: 404 }); + return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); } else { return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); } @@ -34,7 +34,6 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial.json +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json new file mode 100644 index 00000000000000..38b3ed9f746967 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json @@ -0,0 +1,11 @@ +{ + "name": "Query which is disabled", + "description": "Example query which will is disabled and will not run after being posted", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "enabled": false +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json new file mode 100644 index 00000000000000..681d66e16d0ba0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json @@ -0,0 +1,11 @@ +{ + "name": "Query which is immutable", + "description": "Example query which is immutable", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "immutable": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json new file mode 100644 index 00000000000000..ed8849831a4793 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json @@ -0,0 +1,11 @@ +{ + "name": "Query with the language set to lucene", + "description": "Query with the language set to lucene", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "language": "lucene" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json similarity index 74% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json index 9ca4c326313342..721a172ce55d75 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json @@ -1,17 +1,12 @@ { - "rule_id": "rule-1", - "description": "Detecting root and admin users", - "index": ["auditbeat-*", "filebeat-*", "packetbeat-*", "winlogbeat-*"], - "interval": "5s", - "name": "Detect Root/Admin Users", - "severity": "high", + "name": "Query which has Mitre Attack Data", + "description": "Example query which has Mitre Attack Data as threats", "risk_score": 1, + "severity": "high", "type": "query", - "from": "now-6s", + "from": "now-6m", "to": "now", "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"], "threats": [ { "framework": "MITRE ATT&CK", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json new file mode 100644 index 00000000000000..adb96ba398f5e9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json @@ -0,0 +1,80 @@ +{ + "name": "Query with all possible fields filled out", + "description": "Kitchen Sink (everything) query that has all possible fields filled out", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "rule-id-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json similarity index 62% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json index eb7f2ae03b64b1..c754ab73ea21e8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json @@ -1,15 +1,13 @@ { - "rule_id": "rule-5", - "risk_score": 5, - "description": "Detecting root and admin users over 24 hours on windows", - "interval": "5m", - "name": "Detect Root/Admin Users", + "name": "Query with two filters", + "description": "A KQL Query with a two filters", + "rule_id": "query-with-two-filters", + "risk_score": 15, "severity": "high", "type": "query", "from": "now-24h", "to": "now", "query": "user.name: root or user.name: admin", - "language": "kuery", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json new file mode 100644 index 00000000000000..f9f5bf854e45c1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json @@ -0,0 +1,17 @@ +{ + "name": "Query which has extra meta data", + "description": "Query which has extra meta data", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "meta": { + "whatever-you-want": { + "store-stateful-stuff-for-ui": { + "or-anything-else": true + } + } + } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json new file mode 100644 index 00000000000000..e4da1960075275 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json @@ -0,0 +1,11 @@ +{ + "name": "Query with a rule id", + "description": "Query with a rule_id that acts like an external id", + "rule_id": "query-rule-id", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json new file mode 100644 index 00000000000000..61e68f886ffe72 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json @@ -0,0 +1,18 @@ +{ + "name": "Simplest Filter", + "description": "Simplest filter with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + } + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json new file mode 100644 index 00000000000000..e812b031a28fd0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json @@ -0,0 +1,10 @@ +{ + "name": "Simplest Query", + "description": "Simplest query with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json deleted file mode 100644 index b00a5929d9ef1a..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 1, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json deleted file mode 100644 index 657439104e3064..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json deleted file mode 100644 index 137cf7eedbccf1..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-2", - "risk_score": 2, - "description": "Detecting root and admin users over a long period of time", - "interval": "24h", - "name": "Detect Root/Admin Users over a long period of time", - "severity": "high", - "type": "query", - "from": "now-1y", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json deleted file mode 100644 index b9160c95621ee8..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-3", - "risk_score": 3, - "description": "Detecting root and admin users as an empty set", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-16y", - "to": "now-15y", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json deleted file mode 100644 index 364e7f00c95714..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-4", - "risk_score": 4, - "description": "Detecting root and admin users with lucene", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "lucene", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json deleted file mode 100644 index de24263c6af5cd..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "rule_id": "rule-8", - "risk_score": 8, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "enabled": false, - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json deleted file mode 100644 index 9bf2b1abf5f909..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "rule_id": "rule-9", - "risk_score": 9, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "enabled": false, - "tags": ["tag_1", "tag_2"], - "false_positives": ["false_1", "false_2"], - "immutable": true, - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json deleted file mode 100644 index 2381e9e259c07d..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "rule_id": "rule-9999", - "risk_score": 100, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "filter", - "from": "now-6m", - "to": "now", - "filter": { - "bool": { - "must": [], - "filter": [ - { - "bool": { - "should": [ - { - "match_phrase": { - "host.name": "siem-windows" - } - } - ], - "minimum_should_match": 1 - } - }, - { - "match_phrase": { - "winlog.event_id": { - "query": "100" - } - } - }, - { - "match_phrase": { - "agent.hostname": { - "query": "siem-windows" - } - } - } - ], - "should": [], - "must_not": [] - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json deleted file mode 100644 index ee8fe1fc93fb36..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "rule_id": "rule-9999", - "risk_score": 100, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "filter", - "from": "now-6m", - "to": "now", - "filter": { - "bool": { - "should": [ - { - "bool": { - "should": [ - { - "match_phrase": { - "user.name": "root" - } - } - ], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [ - { - "match_phrase": { - "user.name": "admin" - } - } - ], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json deleted file mode 100644 index ed8f2e5745bea1..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "rule_id": "rule-meta-data", - "risk_score": 1, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"], - "meta": { - "anything_i_want": { - "total_meta_for_ui_needs": true - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json deleted file mode 100644 index 721644acd989df..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rule_id": "saved-query-1", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json deleted file mode 100644 index b733b6bb8c592f..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "saved-query-2", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid-2", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json deleted file mode 100644 index df1b37f19bf291..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rule_id": "saved-query-3", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid-3" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json deleted file mode 100644 index 09ddfb1c34a924..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 98, - "description": "Changed Description of only detecting root user", - "interval": "50m", - "name": "A different name", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now-5m", - "query": "user.name: root", - "language": "kuery", - "references": ["https://update1.example.com", "https://update2.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json deleted file mode 100644 index 8a3c765519ef36..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 78, - "description": "Changed Description of only detecting root user", - "interval": "50m", - "name": "A different name", - "severity": "high", - "type": "query", - "false_positives": ["false_update_1", "false_update_2"], - "from": "now-6m", - "immutable": true, - "tags": ["some other tag for you"], - "to": "now-5m", - "query": "user.name: root", - "language": "kuery", - "references": ["https://update1.example.com", "https://update2.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md new file mode 100644 index 00000000000000..bdd4f33c35c789 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md @@ -0,0 +1,32 @@ +These are example POST rules that have the ability to use saved query id's and optionally +queries and filters. If you only use a saved query id, then detection engine relies on that +saved query id existing or it will throw errors if the user deletes the saved query id. If you +add a saved query id along side with a filter and/or query then it will try to use the saved query +id first and if that fails it will fall back on the provided filter and/or query. + +Every single json file should have the field: + +```sh +"type": "saved_query" +``` + +set which is what designates it as a type of saved_query + +To post all of them to see in the UI, with the scripts folder as your current working directory: + +```sh +./post_rule.sh ./rules/saved_queries/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/saved_queries/.json +``` + +If the saved_id does not exist and you do not provide a query and/or filter then expect to see this +in your kibana console logging: + +```sh +server log [11:48:33.331] [error][task_manager] Task alerting:siem.signals "fedc2390-1858-11ea-9184-15f04d7099dc" failed: Error: Saved object [query/test-saved-id] not found +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json new file mode 100644 index 00000000000000..0e0be24c00207c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json @@ -0,0 +1,11 @@ +{ + "name": "Simplest Saved Query that uses a rule-id", + "rule_id": "simplest-saved-query", + "description": "Using a saved_id called test-saved-id but does not provide a pre-defined filter or query", + "risk_score": 5, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json new file mode 100644 index 00000000000000..c4aaf4aae93c26 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json @@ -0,0 +1,81 @@ +{ + "name": "Saved Query with all possible fields filled out", + "description": "Kitchen Sink (everything) saved query that has all possible fields filled out", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "saved-query-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "saved_query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json new file mode 100644 index 00000000000000..55f95e9644b8b3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json @@ -0,0 +1,19 @@ +{ + "name": "Simplest saved id with filters", + "description": "Simplest saved_id with a filter for a fallback if the saved_id is not found", + "risk_score": 1, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + } + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json new file mode 100644 index 00000000000000..ee37c4cb784d1a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json @@ -0,0 +1,11 @@ +{ + "name": "Simplest Saved Query", + "description": "Simplest saved query with a fallback of a query", + "risk_score": 1, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json new file mode 100644 index 00000000000000..19801e7a98ac20 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json @@ -0,0 +1,26 @@ +{ + "name": "Query with filter", + "description": "A KQL Query with a two filters", + "rule_id": "query-with-two-filters", + "risk_score": 15, + "severity": "high", + "type": "query", + "from": "now-24h", + "to": "now", + "query": "user.name: root or user.name: admin", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json new file mode 100644 index 00000000000000..a3dbf0f1b09af6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json @@ -0,0 +1,10 @@ +{ + "name": "Simplest Saved Query", + "description": "Using a saved_id called test-saved-id but does not provide a pre-defined filter or query", + "risk_score": 5, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md new file mode 100644 index 00000000000000..8b6508c64dc5c5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md @@ -0,0 +1,18 @@ +These are example POST rules that are more either Kibana UI test cases, mis-use test cases, +any generic e2e based test cases for testing different scenarios. Normally you would not +use these type of rule based messages when writing pure REST API calls. These messages are +more of what you would see "behind the scenes" when you are using Kibana UI which can +create rules with additional "meta" data or other implementation details that aren't really +a concern for a regular REST API user. + +To post all of them to see in the UI, with the scripts folder as your current working directory: + +```sh +./post_rule.sh ./rules/test_cases/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/test_cases/.json +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_with_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_with_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_without_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_without_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json similarity index 80% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json index 81ec19a4fd0ef0..ced59c3ef2c7ba 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json @@ -1,9 +1,9 @@ { - "rule_id": "rule-7", + "name": "Query Filter With UI Meta Data With Lucene as a language", + "description": "A Query Filter that has lots of UI meta data state in the filters which goes back by 24 hours", + "rule_id": "query-filter-ui-lucene", "risk_score": 7, - "description": "Detecting root and admin users", "interval": "5m", - "name": "Detect Root/Admin Users", "severity": "high", "type": "query", "from": "now-24h", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json similarity index 82% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json index 94f30bc9f92df2..21fe7f7e2557a3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json @@ -1,9 +1,9 @@ { - "rule_id": "rule-6", + "name": "Query Filter With UI Meta Data", + "description": "A Query Filter that has lots of UI meta data state in the filters which goes back by 24 hours", + "rule_id": "query-filter-ui-kuery", "risk_score": 6, - "description": "Detecting root and admin users", "interval": "5m", - "name": "Detect Root/Admin Users", "severity": "high", "type": "query", "from": "now-24h", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json new file mode 100644 index 00000000000000..607dcd963780c9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json @@ -0,0 +1,36 @@ +{ + "type": "saved_query", + "index": ["auditbeat-*", "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*"], + "language": "kuery", + "filters": [ + { + "meta": { + "alias": null, + "negate": false, + "disabled": false, + "type": "phrase", + "key": "source.ip", + "params": { "query": "127.0.0.1" } + }, + "query": { + "match": { "source.ip": { "query": "127.0.0.1", "type": "phrase" } } + }, + "$state": { "store": "appState" } + } + ], + "output_index": ".siem-signals-default", + "query": "", + "saved_id": "User's IP", + "false_positives": [], + "references": [], + "risk_score": 21, + "name": "Signal Maker 5000", + "description": "It's the Garrett", + "severity": "low", + "tags": ["Spong"], + "interval": "5m", + "from": "now-300s", + "enabled": true, + "to": "now", + "meta": { "from": "now-300s" } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md new file mode 100644 index 00000000000000..97a5d31bb01330 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md @@ -0,0 +1,25 @@ +These are example PUT rules to see how to update various parts of the rules. +You either have to use the id, or you have to use the rule_id in order to update +the rules. rule_id acts as an external_id where you can update rules across different +Kibana systems where id acts as a normal server generated id which is not normally shared +across different Kibana systems. + +The only thing you cannot update is the `rule_id` or regular `id` of the system. If `rule_id` +is incorrect then you have to delete the rule completely and re-initialize it with the +correct `rule_id` + +First add all the examples from queries like so: + +```sh +./post_rule.sh ./rules/queries/*.json +``` + +Then to selectively update a rule add the file of your choosing to update: + +```sh +./update_rule.sh ./rules/updates/.json +``` + +Take note that the ones with "id" must be changed to a GUID that only you know about through +a `./find_rules.sh`. For example to grab a GUID id off of the first found record that exists +you can do: `./find_rules.sh | jq '.data[0].id'` and then replace the id in `updates/simplest_update_risk_score_by_id.json` with that particular id to watch it happen. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json new file mode 100644 index 00000000000000..a94558143882b1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "enabled": false +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json new file mode 100644 index 00000000000000..bfe7c7f546fc3b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json new file mode 100644 index 00000000000000..00966ddba7c7ab --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json @@ -0,0 +1,4 @@ +{ + "id": "ade31ba8-dc49-4c18-b7f4-370b35df5f57", + "risk_score": 38 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json new file mode 100644 index 00000000000000..ad3c78183297d0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "risk_score": 98 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json new file mode 100644 index 00000000000000..56c9f151dc7129 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "name": "Changes only the name to this new value" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json new file mode 100644 index 00000000000000..285e9c94a76d9a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json @@ -0,0 +1,80 @@ +{ + "name": "Updates a query with all possible fields that can be updated", + "description": "Kitchen Sink (everything) query that has all possible fields filled out.", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "rule-id-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json deleted file mode 100644 index a43398bd6876a9..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-longmont", - "risk_score": 5, - "description": "Detect Longmont activity", - "interval": "24h", - "name": "Detect Longmont activity", - "severity": "high", - "type": "query", - "from": "now-1y", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh index 8e1abc70456020..aa22db965664a5 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh @@ -10,11 +10,11 @@ set -e ./check_env_variables.sh # Uses a default if no argument is specified -RULES=(${@:-./rules/root_or_admin_update_1.json}) +RULES=(${@:-./rules/updates/simplest_updated_name.json}) # Example: ./update_rule.sh -# Example: ./update_rule.sh ./rules/root_or_admin_1.json -# Example glob: ./post_rule.sh ./rules/* +# Example: ./update_rule.sh ./rules/updates/simplest_updated_name.json +# Example glob: ./post_rule.sh ./rules/updates/* for RULE in "${RULES[@]}" do { [ -e "$RULE" ] || continue From 25c750b2252d1a6ac5685789fbb34b0c6f34317b Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 9 Dec 2019 12:43:52 -0700 Subject: [PATCH 18/36] Cancel discarded KQL value suggestion requests (#51411) * Fix filter matches index for filters with partial meta * Abort discarded KQL value suggestion requests * Abort server-side connection to ES * Fix failing test --- .../suggestions/register_value_suggestions.js | 7 +++--- .../public/autocomplete_provider/types.ts | 1 + .../suggestions_provider/value_suggestions.ts | 19 ++++++++++++--- .../query_string_input/query_string_input.tsx | 23 ++++++++++++++----- .../public/autocomplete_providers/index.js | 4 ++-- .../public/autocomplete_providers/value.js | 4 ++-- .../autocomplete_providers/value.test.js | 2 +- 7 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js index 4ffe790bd51a18..1f5ed443fee80e 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js @@ -18,6 +18,7 @@ */ import { get, map } from 'lodash'; +import { abortableRequestHandler } from '../../../../../elasticsearch/lib/abortable_request_handler'; export function registerValueSuggestions(server) { const serverConfig = server.config(); @@ -26,7 +27,7 @@ export function registerValueSuggestions(server) { server.route({ path: '/api/kibana/suggestions/values/{index}', method: ['POST'], - handler: async function (req) { + handler: abortableRequestHandler(async function (signal, req) { const { index } = req.params; const { field: fieldName, query, boolFilter } = req.payload; const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); @@ -46,7 +47,7 @@ export function registerValueSuggestions(server) { ); try { - const response = await callWithRequest(req, 'search', { index, body }); + const response = await callWithRequest(req, 'search', { index, body }, { signal }); const buckets = get(response, 'aggregations.suggestions.buckets') || get(response, 'aggregations.nestedSuggestions.suggestions.buckets') || []; @@ -55,7 +56,7 @@ export function registerValueSuggestions(server) { } catch (error) { throw server.plugins.elasticsearch.handleESError(error); } - }, + }), }); } diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete_provider/types.ts index 3d34b1bc4a2d2a..389057f94144d2 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete_provider/types.ts @@ -40,6 +40,7 @@ export type GetSuggestions = (args: { query: string; selectionStart: number; selectionEnd: number; + signal?: AbortSignal; }) => Promise; /** @public **/ diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.ts index 282f4ee65dc96a..68076cd43c336b 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/suggestions_provider/value_suggestions.ts @@ -28,23 +28,36 @@ export function getSuggestionsProvider( http: HttpServiceBase ): IGetSuggestions { const requestSuggestions = memoize( - (index: string, field: IFieldType, query: string, boolFilter: any = []) => { + ( + index: string, + field: IFieldType, + query: string, + boolFilter: any = [], + signal?: AbortSignal + ) => { return http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, field: field.name, boolFilter }), + signal, }); }, resolver ); - return async (index: string, field: IFieldType, query: string, boolFilter?: any) => { + return async ( + index: string, + field: IFieldType, + query: string, + boolFilter?: any, + signal?: AbortSignal + ) => { const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues'); if (field.type === 'boolean') { return [true, false]; } else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') { return []; } - return await requestSuggestions(index, field, query, boolFilter); + return await requestSuggestions(index, field, query, boolFilter, signal); }; } diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index dcc07f4fd43c5b..16b22a164f2f02 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -106,6 +106,7 @@ export class QueryStringInputUI extends Component { public inputRef: HTMLInputElement | null = null; private persistedLog: PersistedLog | undefined; + private abortController: AbortController | undefined; private services = this.props.kibana.services; private componentIsUnmounting = false; @@ -163,12 +164,22 @@ export class QueryStringInputUI extends Component { return; } - const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ - query: queryString, - selectionStart, - selectionEnd, - }); - return [...suggestions, ...recentSearchSuggestions]; + try { + if (this.abortController) this.abortController.abort(); + this.abortController = new AbortController(); + const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ + query: queryString, + selectionStart, + selectionEnd, + signal: this.abortController.signal, + }); + return [...suggestions, ...recentSearchSuggestions]; + } catch (e) { + // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error + // Ignore aborted requests + if (e.message === 'The user aborted a request.') return; + throw e; + } }; private getRecentSearchSuggestions = (query: string) => { diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js index 7b0e42283d5f59..100fa92a399908 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js @@ -22,7 +22,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { return provider({ config, indexPatterns, boolFilter }); }); - return function getSuggestions({ query, selectionStart, selectionEnd }) { + return function getSuggestions({ query, selectionStart, selectionEnd, signal }) { const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr(selectionEnd)}`; let cursorNode; @@ -34,7 +34,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { const { suggestionTypes = [] } = cursorNode; const suggestionsByType = suggestionTypes.map(type => { - return getSuggestionsByType[type](cursorNode); + return getSuggestionsByType[type](cursorNode, signal); }); return Promise.all(suggestionsByType) .then(suggestionsByType => dedup(flatten(suggestionsByType))); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js index 52bf347a94075c..88e2fe812faa61 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js @@ -27,14 +27,14 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { suffix, fieldName, nestedPath, - }) { + }, signal) { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; const fields = allFields.filter(field => field.name === fullFieldName); const query = `${prefix}${suffix}`.trim(); const { getSuggestions } = npStart.plugins.data; const suggestionsByField = fields.map(field => { - return getSuggestions(field.indexPatternTitle, field, query, boolFilter).then(data => { + return getSuggestions(field.indexPatternTitle, field, query, boolFilter, signal).then(data => { const quotedValues = data.map(value => typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}`); return wrapAsSuggestions(start, end, query, quotedValues); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js index 591833d6463606..771984a1233e30 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js @@ -112,7 +112,7 @@ describe('Kuery value suggestions', function () { const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); await getSuggestions({ fieldName, prefix, suffix }); expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined); + expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined, undefined); }); test('should escape quotes in suggestions', async () => { From 942f5420ed3c8c30457929cda1b1491e59510d3c Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Mon, 9 Dec 2019 15:03:18 -0500 Subject: [PATCH 19/36] provide finer detail on action execution errors (#52146) resolves https://github.com/elastic/kibana/issues/52103 --- .../server/action_type_registry.test.ts | 2 +- .../actions/server/actions_client.test.ts | 2 +- .../server/builtin_action_types/email.ts | 10 +- .../server/builtin_action_types/es_index.ts | 16 ++-- .../builtin_action_types/pagerduty.test.ts | 93 ++++++++++--------- .../server/builtin_action_types/pagerduty.ts | 19 ++-- .../server/builtin_action_types/server_log.ts | 10 +- .../server/builtin_action_types/slack.ts | 62 +++++++------ .../server/builtin_action_types/webhook.ts | 55 +++++------ .../server/lib/action_executor.test.ts | 2 + .../actions/server/lib/action_executor.ts | 4 +- .../server/lib/task_runner_factory.test.ts | 7 +- .../server/lib/validate_with_schema.test.ts | 2 +- .../actions/server/routes/execute.test.ts | 4 +- x-pack/legacy/plugins/actions/server/types.ts | 2 + .../translations/translations/ja-JP.json | 16 ---- .../translations/translations/zh-CN.json | 16 ---- .../common/fixtures/plugins/alerts/index.ts | 4 +- .../actions/builtin_action_types/es_index.ts | 2 +- .../actions/builtin_action_types/pagerduty.ts | 11 +-- .../actions/builtin_action_types/slack.ts | 7 +- .../actions/builtin_action_types/webhook.ts | 5 +- 22 files changed, 157 insertions(+), 194 deletions(-) diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts b/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts index b4d73cc4759d77..c0a01bc85e9169 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts @@ -18,7 +18,7 @@ const actionTypeRegistryParams = { beforeEach(() => jest.resetAllMocks()); const executor: ExecutorType = async options => { - return { status: 'ok' }; + return { status: 'ok', actionId: options.actionId }; }; describe('register()', () => { diff --git a/x-pack/legacy/plugins/actions/server/actions_client.test.ts b/x-pack/legacy/plugins/actions/server/actions_client.test.ts index 1c10d1b1a83af2..1cbf3949d20f8c 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.test.ts +++ b/x-pack/legacy/plugins/actions/server/actions_client.test.ts @@ -30,7 +30,7 @@ const actionTypeRegistryParams = { let actionsClient: ActionsClient; let actionTypeRegistry: ActionTypeRegistry; const executor: ExecutorType = async options => { - return { status: 'ok' }; + return { status: 'ok', actionId: options.actionId }; }; beforeEach(() => { diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts index a378d8a4b9b558..dd2bd328ce53fe 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts @@ -174,19 +174,17 @@ async function executor( result = await sendEmail(logger, sendEmailOptions); } catch (err) { const message = i18n.translate('xpack.actions.builtin.email.errorSendingErrorMessage', { - defaultMessage: 'error in action "{actionId}" sending email: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error sending email', }); return { status: 'error', + actionId, message, + serviceMessage: err.message, }; } - return { status: 'ok', data: result }; + return { status: 'ok', data: result, actionId }; } // utilities diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts index 01853ee9cb870b..0e9fe0483ee1ea 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts @@ -60,13 +60,11 @@ async function executor( if (config.index == null && params.index == null) { const message = i18n.translate('xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage', { - defaultMessage: 'index param needs to be set because not set in config for action {actionId}', - values: { - actionId, - }, + defaultMessage: 'index param needs to be set because not set in config for action', }); return { status: 'error', + actionId, message, }; } @@ -101,17 +99,15 @@ async function executor( result = await services.callCluster('bulk', bulkParams); } catch (err) { const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { - defaultMessage: 'error in action "{actionId}" indexing data: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error indexing documents', }); return { status: 'error', + actionId, message, + serviceMessage: err.message, }; } - return { status: 'ok', data: result }; + return { status: 'ok', data: result, actionId }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index 1d453d2bd23408..def3b7eea21d96 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -148,11 +148,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should succeed with maximal valid params for trigger', async () => { @@ -212,11 +213,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should succeed with maximal valid params for acknowledge', async () => { @@ -267,11 +269,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should succeed with maximal valid params for resolve', async () => { @@ -322,11 +325,12 @@ describe('execute()', () => { } `); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "data": "data-here", - "status": "ok", - } - `); + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); }); test('should fail when sendPagerdury throws', async () => { @@ -348,11 +352,13 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: doing some testing", - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event", + "serviceMessage": "doing some testing", + "status": "error", + } + `); }); test('should fail when sendPagerdury returns 429', async () => { @@ -374,12 +380,13 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: status 429, retry later", - "retry": true, - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event: http status 429, retry later", + "retry": true, + "status": "error", + } + `); }); test('should fail when sendPagerdury returns 501', async () => { @@ -401,12 +408,13 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: status 501, retry later", - "retry": true, - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event: http status 501, retry later", + "retry": true, + "status": "error", + } + `); }); test('should fail when sendPagerdury returns 418', async () => { @@ -428,10 +436,11 @@ describe('execute()', () => { }; const actionResponse = await actionType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` - Object { - "message": "error in pagerduty action \\"some-action-id\\" posting event: unexpected status 418", - "status": "error", - } - `); + Object { + "actionId": "some-action-id", + "message": "error posting pagerduty event: unexpected status 418", + "status": "error", + } + `); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts index ec45e298d39022..e1437b8eef5542 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -123,16 +123,14 @@ async function executor( response = await postPagerduty({ apiUrl, data, headers, services }); } catch (err) { const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', { - defaultMessage: 'error in pagerduty action "{actionId}" posting event: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error posting pagerduty event', }); logger.warn(`error thrown posting pagerduty event: ${err.message}`); return { status: 'error', + actionId, message, + serviceMessage: err.message, }; } @@ -141,38 +139,37 @@ async function executor( if (response.status === 202) { return { status: 'ok', + actionId, data: response.data, }; } if (response.status === 429 || response.status >= 500) { const message = i18n.translate('xpack.actions.builtin.pagerduty.postingRetryErrorMessage', { - defaultMessage: - 'error in pagerduty action "{actionId}" posting event: status {status}, retry later', + defaultMessage: 'error posting pagerduty event: http status {status}, retry later', values: { - actionId, status: response.status, }, }); return { status: 'error', + actionId, message, retry: true, }; } const message = i18n.translate('xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage', { - defaultMessage: - 'error in pagerduty action "{actionId}" posting event: unexpected status {status}', + defaultMessage: 'error posting pagerduty event: unexpected status {status}', values: { - actionId, status: response.status, }, }); return { status: 'error', + actionId, message, }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts index f17430b734c66b..c2af29051d8dd9 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts @@ -52,17 +52,15 @@ async function executor( logger[params.level](params.message); } catch (err) { const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', { - defaultMessage: 'error in action "{actionId}" logging message: {errorMessage}', - values: { - actionId, - errorMessage: err.message, - }, + defaultMessage: 'error logging message', }); return { status: 'error', message, + serviceMessage: err.message, + actionId, }; } - return { status: 'ok' }; + return { status: 'ok', actionId }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts index 8ec569a69ff235..29b89150e3990f 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts @@ -69,7 +69,7 @@ async function slackExecutor( result = await webhook.send(message); } catch (err) { if (err.original == null || err.original.response == null) { - return errorResult(actionId, err.message); + return serviceErrorResult(actionId, err.message); } const { status, statusText, headers } = err.original.response; @@ -88,7 +88,17 @@ async function slackExecutor( ); } - return errorResult(actionId, `${err.message} - ${statusText}`); + const errMessage = i18n.translate( + 'xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage', + { + defaultMessage: 'unexpected http response from slack: {httpStatus} {httpStatusText}', + values: { + httpStatus: status, + httpStatusText: statusText, + }, + } + ); + return errorResult(actionId, errMessage); } if (result == null) { @@ -102,55 +112,52 @@ async function slackExecutor( } if (result.text !== 'ok') { - const errMessage = i18n.translate( - 'xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage', - { - defaultMessage: 'unexpected text response from slack', - } - ); - return errorResult(actionId, errMessage); + return serviceErrorResult(actionId, result.text); } - return successResult(result); + return successResult(actionId, result); } -function successResult(data: any): ActionTypeExecutorResult { - return { status: 'ok', data }; +function successResult(actionId: string, data: any): ActionTypeExecutorResult { + return { status: 'ok', data, actionId }; } -function errorResult(id: string, message: string): ActionTypeExecutorResult { +function errorResult(actionId: string, message: string): ActionTypeExecutorResult { + return { + status: 'error', + message, + actionId, + }; +} +function serviceErrorResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.slack.errorPostingErrorMessage', { - defaultMessage: 'an error occurred in action "{id}" posting a slack message: {message}', - values: { - id, - message, - }, + defaultMessage: 'error posting slack message', }); return { status: 'error', message: errMessage, + actionId, + serviceMessage, }; } -function retryResult(id: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, message: string): ActionTypeExecutorResult { const errMessage = i18n.translate( 'xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage', { - defaultMessage: 'an error occurred in action "{id}" posting a slack message, retry later', - values: { - id, - }, + defaultMessage: 'error posting a slack message, retry later', } ); return { status: 'error', message: errMessage, retry: true, + actionId, }; } function retryResultSeconds( - id: string, + actionId: string, message: string, retryAfter: number ): ActionTypeExecutorResult { @@ -160,12 +167,9 @@ function retryResultSeconds( const errMessage = i18n.translate( 'xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage', { - defaultMessage: - 'an error occurred in action "{id}" posting a slack message, retry at {retryString}: {message}', + defaultMessage: 'error posting a slack message, retry at {retryString}', values: { - id, retryString, - message, }, } ); @@ -173,5 +177,7 @@ function retryResultSeconds( status: 'error', message: errMessage, retry, + actionId, + serviceMessage: message, }; } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts index b91fd86b0b6824..06fe2fb0e591c0 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts @@ -113,7 +113,7 @@ export async function executor( } = result; logger.debug(`response from webhook action "${actionId}": [HTTP ${status}] ${statusText}`); - return successResult(data); + return successResult(actionId, data); } else { const { error } = result; @@ -139,70 +139,58 @@ export async function executor( return errorResultInvalid(actionId, message); } - const message = i18n.translate('xpack.actions.builtin.webhook.unreachableRemoteWebhook', { - defaultMessage: 'Unreachable Remote Webhook, are you sure the address is correct?', - }); - logger.warn(`error on ${actionId} webhook action: ${message}`); - return errorResultUnreachable(actionId, message); + logger.warn(`error on ${actionId} webhook action: unexpected error`); + return errorResultUnexpectedError(actionId); } } // Action Executor Result w/ internationalisation -function successResult(data: any): ActionTypeExecutorResult { - return { status: 'ok', data }; +function successResult(actionId: string, data: any): ActionTypeExecutorResult { + return { status: 'ok', data, actionId }; } -function errorResultInvalid(id: string, message: string): ActionTypeExecutorResult { +function errorResultInvalid(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.webhook.invalidResponseErrorMessage', { - defaultMessage: - 'Invalid Response: an error occurred in webhook action "{id}" calling a remote webhook: {message}', - values: { - id, - message, - }, + defaultMessage: 'error calling webhook, invalid response', }); return { status: 'error', message: errMessage, + actionId, + serviceMessage, }; } -function errorResultUnreachable(id: string, message: string): ActionTypeExecutorResult { +function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.webhook.unreachableErrorMessage', { - defaultMessage: - 'Unreachable Webhook: an error occurred in webhook action "{id}" calling a remote webhook: {message}', - values: { - id, - message, - }, + defaultMessage: 'error calling webhook, unexpected error', }); return { status: 'error', message: errMessage, + actionId, }; } -function retryResult(id: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate( 'xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage', { - defaultMessage: - 'Invalid Response: an error occurred in webhook action "{id}" calling a remote webhook, retry later', - values: { - id, - }, + defaultMessage: 'error calling webhook, retry later', } ); return { status: 'error', message: errMessage, retry: true, + actionId, + serviceMessage, }; } function retryResultSeconds( - id: string, - message: string, + actionId: string, + serviceMessage: string, retryAfter: number ): ActionTypeExecutorResult { @@ -212,12 +200,9 @@ function retryResultSeconds( const errMessage = i18n.translate( 'xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage', { - defaultMessage: - 'Invalid Response: an error occurred in webhook action "{id}" calling a remote webhook, retry at {retryString}: {message}', + defaultMessage: 'error calling webhook, retry at {retryString}', values: { - id, retryString, - message, }, } ); @@ -225,5 +210,7 @@ function retryResultSeconds( status: 'error', message: errMessage, retry, + actionId, + serviceMessage, }; } diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts index 5ed67ae82b0ce5..6767468509d25c 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts @@ -157,6 +157,7 @@ test('throws an error when config is invalid', async () => { const result = await actionExecutor.execute(executeParams); expect(result).toEqual({ + actionId: '1', status: 'error', retry: false, message: `error validating action type config: [param1]: expected value of type [string] but got [undefined]`, @@ -188,6 +189,7 @@ test('throws an error when params is invalid', async () => { const result = await actionExecutor.execute(executeParams); expect(result).toEqual({ + actionId: '1', status: 'error', retry: false, message: `error validating action params: [param1]: expected value of type [string] but got [undefined]`, diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts index 1afb8a8870215a..c532b76a904d5a 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts @@ -91,7 +91,7 @@ export class ActionExecutor { validatedConfig = validateConfig(actionType, config); validatedSecrets = validateSecrets(actionType, secrets); } catch (err) { - return { status: 'error', message: err.message, retry: false }; + return { status: 'error', actionId, message: err.message, retry: false }; } let result: ActionTypeExecutorResult | null = null; @@ -113,7 +113,7 @@ export class ActionExecutor { logger.debug(`action executed successfully: ${actionLabel}`); // return basic response if none provided - if (result == null) return { status: 'ok' }; + if (result == null) return { status: 'ok', actionId }; return result; } diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index a5bf42bc2cc019..41a7c17a02c5a3 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -94,7 +94,7 @@ test('executes the task by calling the executor with proper parameters', async ( taskInstance: mockedTaskInstance, }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', @@ -154,6 +154,7 @@ test('throws an error with suggested retry logic when return status is error', a }); mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'error', + actionId: '2', message: 'Error message', data: { foo: true }, retry: false, @@ -174,7 +175,7 @@ test('uses API key when provided', async () => { taskInstance: mockedTaskInstance, }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', @@ -217,7 +218,7 @@ test(`doesn't use API key when not provided`, async () => { factory.initialize(taskRunnerFactoryInitializerParams); const taskRunner = factory.create({ taskInstance: mockedTaskInstance }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', diff --git a/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts b/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts index 4cb28728fb4216..28122c72baf654 100644 --- a/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts @@ -10,7 +10,7 @@ import { validateParams, validateConfig, validateSecrets } from './validate_with import { ActionType, ExecutorType } from '../types'; const executor: ExecutorType = async options => { - return { status: 'ok' }; + return { status: 'ok', actionId: options.actionId }; }; test('should validate when there are no validators', () => { diff --git a/x-pack/legacy/plugins/actions/server/routes/execute.test.ts b/x-pack/legacy/plugins/actions/server/routes/execute.test.ts index cd5b9c7c4a7e84..b0ba5a8a0f5668 100644 --- a/x-pack/legacy/plugins/actions/server/routes/execute.test.ts +++ b/x-pack/legacy/plugins/actions/server/routes/execute.test.ts @@ -31,11 +31,11 @@ it('executes an action with proper parameters', async () => { callCluster: jest.fn(), savedObjectsClient: jest.fn(), }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok' }); + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '1' }); const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); - expect(payload).toBe('{"status":"ok"}'); + expect(JSON.parse(payload)).toEqual({ status: 'ok', actionId: '1' }); expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ actionId: '1', diff --git a/x-pack/legacy/plugins/actions/server/types.ts b/x-pack/legacy/plugins/actions/server/types.ts index 5a74241bc4829c..94b34034cd8b23 100644 --- a/x-pack/legacy/plugins/actions/server/types.ts +++ b/x-pack/legacy/plugins/actions/server/types.ts @@ -51,8 +51,10 @@ export interface FindActionResult extends ActionResult { // the result returned from an action type executor function export interface ActionTypeExecutorResult { + actionId: string; status: 'ok' | 'error'; message?: string; + serviceMessage?: string; data?: any; retry?: null | boolean | Date; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b9142c821051f2..d51332c65aa544 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12047,23 +12047,7 @@ "visTypeMarkdown.params.fontSizeLabel": "ポイント単位のベースフォントサイズです。", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "アクションタイプ \"{id}\" は登録されていません。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "アクションタイプ \"{id}\" は既に登録されています。", - "xpack.actions.builtin.email.errorSendingErrorMessage": "アクション「{actionId}」メールの送信中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "アクション「{actionId}」データのインデックス中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage": "アクション {actionId} の構成で設定されていないため、インデックスパラメーターの設定が必要です", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: ステータス {status}、後程再試行してください", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: 予期せぬステータス {status}", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "アクション「{actionId}」メッセージのログ記録宙にエラーが発生: {errorMessage}", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました: {message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました: {retryString} に再試行してください: {message}", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました。後程再試行してください", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "Slack から予期せぬ null 応答", - "xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage": "Slack から予期せぬテキスト応答", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "無効な応答:Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました: {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "無効な応答: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました。{retryString} で後程再試行してください: {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "無効な応答: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました。後程再試行してください", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "到達不能な Web フック: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました: {message}", - "xpack.actions.builtin.webhook.unreachableRemoteWebhook": "リモートウェブフックにアクセスできません。アドレスが正しいことを確認してください。", "xpack.actions.builtin.webhook.webhookConfigurationError": "Web フックアクションの構成中にエラーが発生: {message}", "xpack.actions.urlWhitelistConfigurationError": "ターゲット {field} 「{value}」は Kibana のホワイトリストに登録されていません", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "パネルに追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 109693bbb4b1b4..974467a8d20d04 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12136,23 +12136,7 @@ "visTypeMarkdown.params.fontSizeLabel": "基础字体大小(磅)", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "未注册操作类型“{id}”。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "操作类型“{id}”已注册。", - "xpack.actions.builtin.email.errorSendingErrorMessage": "发送电子邮件时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "索引数据时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage": "需要设置索引参数,因为在操作 {actionId} 的配置中未设置", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:状态 {status},请稍后重试", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:异常错误 {status}", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "记录消息时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "发布 slack 消息时操作“{id}”出现错误:{message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "发布 slack 消息时操作“{id}”出现错误,请在 {retryString} 重试:{message}", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "发布 slack 消息时操作“{id}”出现错误,请稍后重试", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "来自 slack 的异常空响应", - "xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage": "来自 slack 的异常文本响应", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误,请在 {retryString} 重试:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误,请稍后重试", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "Webhook无法访问:调用远程 Webhook 时 Webhook 操作“{id}”发生错误:{message}", - "xpack.actions.builtin.webhook.unreachableRemoteWebhook": "远程 Webhook 无法访问,是否确定地址正确?", "xpack.actions.builtin.webhook.webhookConfigurationError": "配置 Webhook 操作时出错:{message}", "xpack.actions.urlWhitelistConfigurationError": "目标 {field}“{value}”不在 Kibana 白名单中", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "添加到面板", diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts index b2e69a21218256..30b235a784c224 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts @@ -19,7 +19,7 @@ export default function(kibana: any) { id: 'test.noop', name: 'Test: Noop', async executor() { - return { status: 'ok' }; + return { status: 'ok', actionId: '' }; }, }; const indexRecordActionType: ActionType = { @@ -101,6 +101,7 @@ export default function(kibana: any) { return { status: 'error', retry: new Date(params.retryAt), + actionId: '', }; }, }; @@ -162,6 +163,7 @@ export default function(kibana: any) { }); return { status: 'ok', + actionId: '', }; }, }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index 197ad3ff80094a..aacf0b8f87ed01 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -234,7 +234,7 @@ export default function indexTest({ getService }: FtrProviderContext) { result = response.body; expect(result.status).to.equal('error'); expect(result.message).to.eql( - `index param needs to be set because not set in config for action ${createdActionID}` + 'index param needs to be set because not set in config for action' ); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts index 62c8b31209729f..54f38d2fd747e3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts @@ -121,6 +121,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { .expect(200); expect(result).to.eql({ status: 'ok', + actionId: simulatedActionId, data: { dedup_key: `action:${simulatedActionId}`, message: 'Event processed', @@ -140,9 +141,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match( - /error in pagerduty action .+ posting event: unexpected status 418/ - ); + expect(result.message).to.match(/error posting pagerduty event: unexpected status 418/); }); it('should handle a 429 pagerduty error', async () => { @@ -158,7 +157,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(result.status).to.equal('error'); expect(result.message).to.match( - /error in pagerduty action .+ posting event: status 429, retry later/ + /error posting pagerduty event: http status 429, retry later/ ); expect(result.retry).to.equal(true); }); @@ -175,9 +174,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match( - /error in pagerduty action .+ posting event: status 502, retry later/ - ); + expect(result.message).to.match(/error posting pagerduty event: http status 502/); expect(result.retry).to.equal(true); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index ae1464023c0119..288eda5111a1e9 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -126,7 +126,7 @@ export default function slackTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); + expect(result.message).to.match(/unexpected http response from slack: /); }); it('should handle a 429 slack error', async () => { @@ -142,8 +142,7 @@ export default function slackTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); - expect(result.message).to.match(/retry at/); + expect(result.message).to.match(/error posting a slack message, retry at \d\d\d\d-/); const dateRetry = new Date(result.retry).getTime(); expect(dateRetry).to.greaterThan(dateStart); @@ -161,7 +160,7 @@ export default function slackTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); + expect(result.message).to.match(/error posting a slack message, retry later/); expect(result.retry).to.equal(true); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 53b11525d1b95a..426bbe853ca190 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -196,7 +196,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/Unreachable Remote Webhook/); + expect(result.message).to.match(/error calling webhook, unexpected error/); }); it('should handle failing webhook targets', async () => { const webhookActionId = await createWebhookAction(webhookSimulatorURL); @@ -211,7 +211,8 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/Bad Request/); + expect(result.message).to.match(/error calling webhook, invalid response/); + expect(result.serviceMessage).to.eql('[400] Bad Request'); }); }); } From cb60a77bb9c577e78a3ca38e8aa78ebdc58eb6c8 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 9 Dec 2019 13:26:50 -0700 Subject: [PATCH 20/36] [Maps] only show styles that apply to layer feature types in legend (#52335) * [Maps] only show styles that apply to layer feature types in legend * update hasLegendDetails check to include style property filters * clean up --- .../layer_control/layer_toc/toc_entry/view.js | 14 ++- .../layer_toc/toc_entry/view.test.js | 1 + .../maps/public/layers/heatmap_layer.js | 2 +- .../plugins/maps/public/layers/layer.js | 2 +- .../legend/style_property_legend_row.js | 71 ++------------- .../components/legend/vector_style_legend.js | 66 ++++++++++---- .../public/layers/styles/vector/style_util.js | 18 ++++ .../layers/styles/vector/style_util.test.js | 52 ++++++++++- .../layers/styles/vector/vector_style.js | 87 +++++++++++-------- .../styles/vector/vector_style_defaults.js | 3 + .../maps/public/layers/vector_layer.js | 4 +- 11 files changed, 195 insertions(+), 125 deletions(-) diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js index dc0756978010ea..876993eed7795a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js @@ -21,12 +21,14 @@ export class TOCEntry extends React.Component { state = { displayName: null, + hasLegendDetails: false, shouldShowModal: false }; componentDidMount() { this._isMounted = true; this._updateDisplayName(); + this._loadHasLegendDetails(); } componentWillUnmount() { @@ -35,6 +37,7 @@ export class TOCEntry extends React.Component { componentDidUpdate() { this._updateDisplayName(); + this._loadHasLegendDetails(); } _toggleLayerDetailsVisibility = () => { @@ -45,6 +48,13 @@ export class TOCEntry extends React.Component { } } + async _loadHasLegendDetails() { + const hasLegendDetails = await this.props.layer.hasLegendDetails(); + if (this._isMounted && hasLegendDetails !== this.state.hasLegendDetails) { + this.setState({ hasLegendDetails }); + } + } + async _updateDisplayName() { const label = await this.props.layer.getDisplayName(); if (this._isMounted) { @@ -143,7 +153,7 @@ export class TOCEntry extends React.Component { } _renderDetailsToggle() { - if (!this.props.layer.hasLegendDetails()) { + if (!this.state.hasLegendDetails) { return null; } @@ -223,7 +233,7 @@ export class TOCEntry extends React.Component { } _renderLegendDetails = () => { - if (!this.props.isLegendDetailsOpen || !this.props.layer.hasLegendDetails()) { + if (!this.props.isLegendDetailsOpen || !this.state.hasLegendDetails) { return null; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js index ebb9fc27be149b..0c4999ce4e41f5 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js @@ -13,6 +13,7 @@ const LAYER_ID = '1'; const mockLayer = { getId: () => { return LAYER_ID; }, + hasLegendDetails: async () => { return true; }, renderLegendDetails: () => { return (
TOC details mock
); }, getDisplayName: () => { return 'layer 1'; }, isVisible: () => { return true; }, diff --git a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js b/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js index 0342975ce3192f..632ed224740227 100644 --- a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js @@ -98,7 +98,7 @@ export class HeatmapLayer extends VectorLayer { return 'heatmap'; } - hasLegendDetails() { + async hasLegendDetails() { return true; } diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index b1f3c32f267b9e..c646d0ac5c1dff 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -186,7 +186,7 @@ export class AbstractLayer { }; } - hasLegendDetails() { + async hasLegendDetails() { return false; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js index dc5098c4d6d4d9..6bd8b64ddf8328 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js @@ -16,77 +16,16 @@ const EMPTY_VALUE = ''; export class StylePropertyLegendRow extends Component { - state = { - label: '', - hasLoadedFieldFormatter: false, - } - - componentDidMount() { - this._isMounted = true; - this._prevLabel = undefined; - this._fieldValueFormatter = undefined; - this._loadLabel(); - this._loadFieldFormatter(); - } - - componentDidUpdate() { - // label could change so it needs to be loaded on update - this._loadLabel(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async _loadFieldFormatter() { - if (this.props.style.isDynamic() && this.props.style.isComplete() && this.props.style.getField().getSource()) { - const field = this.props.style.getField(); - const source = field.getSource(); - this._fieldValueFormatter = await source.getFieldFormatter(field.getName()); - } else { - this._fieldValueFormatter = null; - } - if (this._isMounted) { - this.setState({ hasLoadedFieldFormatter: true }); - } - } - - _loadLabel = async () => { - if (this._excludeFromHeader()) { - return; - } - - // have to load label and then check for changes since field name stays constant while label may change - const label = await this.props.style.getField().getLabel(); - if (this._prevLabel === label) { - return; - } - - this._prevLabel = label; - if (this._isMounted) { - this.setState({ label }); - } - } - - _excludeFromHeader() { - return !this.props.style.isDynamic() || !this.props.style.isComplete() || !this.props.style.getField().getName(); - } - _formatValue = value => { - if (!this.state.hasLoadedFieldFormatter || !this._fieldValueFormatter || value === EMPTY_VALUE) { + if (!this.props.fieldFormatter || value === EMPTY_VALUE) { return value; } - return this._fieldValueFormatter(value); + return this.props.fieldFormatter(value); } render() { const { range, style } = this.props; - if (this._excludeFromHeader()) { - return null; - } - - const header = style.renderHeader(); const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); const minLabel = this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange ? `< ${min}` : min; @@ -96,17 +35,19 @@ export class StylePropertyLegendRow extends Component { return ( ); } } StylePropertyLegendRow.propTypes = { + label: PropTypes.string, + fieldFormatter: PropTypes.func, range: rangeShape, style: PropTypes.object }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js index e339cad6af9739..5f4139432a9126 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js @@ -4,29 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import _ from 'lodash'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { rangeShape } from '../style_option_shapes'; import { StylePropertyLegendRow } from './style_property_legend_row'; -export function VectorStyleLegend({ styleProperties }) { - return styleProperties.map(styleProperty => { - return ( - - ); - }); -} +export class VectorStyleLegend extends Component { + + state = { + rows: [], + } + + componentDidMount() { + this._isMounted = true; + this._prevRowDescriptors = undefined; + this._loadRows(); + } + + componentDidUpdate() { + this._loadRows(); + } -const stylePropertyShape = PropTypes.shape({ - range: rangeShape, - style: PropTypes.object -}); + componentWillUnmount() { + this._isMounted = false; + } + + _loadRows = _.debounce(async () => { + const rows = await this.props.loadRows(); + const rowDescriptors = rows.map(row => { + return { + label: row.label, + range: row.range, + styleOptions: row.style.getOptions(), + }; + }); + if (this._isMounted && !_.isEqual(rowDescriptors, this._prevRowDescriptors)) { + this._prevRowDescriptors = rowDescriptors; + this.setState({ rows }); + } + }, 100); + + render() { + return this.state.rows.map(rowProps => { + return ( + + ); + }); + } +} VectorStyleLegend.propTypes = { - styleProperties: PropTypes.arrayOf(stylePropertyShape).isRequired + loadRows: PropTypes.func.isRequired, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js index 699955fe6542ac..b8fc428a62a529 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js @@ -12,6 +12,24 @@ export function getComputedFieldNamePrefix(fieldName) { return `__kbn__dynamic__${fieldName}`; } +export function isOnlySingleFeatureType(featureType, supportedFeatures, hasFeatureType) { + if (supportedFeatures.length === 1) { + return supportedFeatures[0] === featureType; + } + + if (!hasFeatureType) { + return false; + } + + const featureTypes = Object.keys(hasFeatureType); + return featureTypes.reduce((isOnlyTargetFeatureType, featureTypeKey) => { + const hasFeature = hasFeatureType[featureTypeKey]; + return featureTypeKey === featureType + ? isOnlyTargetFeatureType && hasFeature + : isOnlyTargetFeatureType && !hasFeature; + }, true); +} + export function scaleValue(value, range) { if (isNaN(value) || !range) { return -1; //Nothing to scale, put outside scaled range diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js index a25e3bf8684c9b..1d6e6b256463e1 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js @@ -4,7 +4,57 @@ * you may not use this file except in compliance with the Elastic License. */ -import { scaleValue } from './style_util'; +import { isOnlySingleFeatureType, scaleValue } from './style_util'; +import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; + +describe('isOnlySingleFeatureType', () => { + describe('source supports single feature type', () => { + const supportedFeatures = [VECTOR_SHAPE_TYPES.POINT]; + + test('Is only single feature type when only supported feature type is target feature type', () => { + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures)).toBe(true); + }); + + test('Is not single feature type when only supported feature type is not target feature type', () => { + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures)).toBe(false); + }); + }); + + describe('source supports multiple feature types', () => { + const supportedFeatures = [ + VECTOR_SHAPE_TYPES.POINT, + VECTOR_SHAPE_TYPES.LINE, + VECTOR_SHAPE_TYPES.POLYGON + ]; + + test('Is only single feature type when data only has target feature type', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: true, + [VECTOR_SHAPE_TYPES.LINE]: false, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)).toBe(true); + }); + + test('Is not single feature type when data has multiple feature types', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: true, + [VECTOR_SHAPE_TYPES.LINE]: true, + [VECTOR_SHAPE_TYPES.POLYGON]: true, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures, hasFeatureType)).toBe(false); + }); + + test('Is not single feature type when data does not have target feature types', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: false, + [VECTOR_SHAPE_TYPES.LINE]: true, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)).toBe(false); + }); + }); +}); describe('scaleValue', () => { test('Should scale value between 0 and 1', () => { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 53794f2043aad3..426af96b63ba24 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -7,7 +7,7 @@ import _ from 'lodash'; import React from 'react'; import { VectorStyleEditor } from './components/vector_style_editor'; -import { getDefaultProperties, VECTOR_STYLES } from './vector_style_defaults'; +import { getDefaultProperties, LINE_STYLES, POLYGON_STYLES, VECTOR_STYLES } from './vector_style_defaults'; import { AbstractStyle } from '../abstract_style'; import { GEO_JSON_TYPE, @@ -21,7 +21,7 @@ import { VectorStyleLegend } from './components/legend/vector_style_legend'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from './vector_constants'; import { getMakiSymbolAnchor } from './symbol_utils'; -import { getComputedFieldName, scaleValue } from './style_util'; +import { getComputedFieldName, isOnlySingleFeatureType, scaleValue } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; import { DynamicSizeProperty } from './properties/dynamic_size_property'; @@ -271,32 +271,23 @@ export class VectorStyle extends AbstractStyle { return styleProperties.filter(styleProperty => (styleProperty.isDynamic() && styleProperty.isComplete())); } - _checkIfOnlyFeatureType = async (featureType) => { - const supportedFeatures = await this._source.getSupportedShapeTypes(); - - if (supportedFeatures.length === 1) { - return supportedFeatures[0] === featureType; - } - - if (!this._descriptor.__styleMeta || !this._descriptor.__styleMeta.hasFeatureType) { - return false; - } - - const featureTypes = Object.keys(this._descriptor.__styleMeta.hasFeatureType); - return featureTypes.reduce((isOnlySingleFeatureType, featureTypeKey) => { - const hasFeature = this._descriptor.__styleMeta.hasFeatureType[featureTypeKey]; - return featureTypeKey === featureType - ? isOnlySingleFeatureType && hasFeature - : isOnlySingleFeatureType && !hasFeature; - }, true); + _isOnlySingleFeatureType = async (featureType) => { + return isOnlySingleFeatureType( + featureType, + await this._source.getSupportedShapeTypes(), + this._getStyleMeta().hasFeatureType); } _getIsPointsOnly = async () => { - return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.POINT); + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT); } _getIsLinesOnly = async () => { - return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.LINE); + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE); + } + + _getIsPolygonsOnly = async () => { + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POLYGON); } _getFieldRange = (fieldName) => { @@ -352,6 +343,10 @@ export class VectorStyle extends AbstractStyle { }; } + _getStyleMeta = () => { + return _.get(this._descriptor, '__styleMeta', {}); + } + getIcon = () => { const styles = this.getRawProperties(); const symbolId = this.arePointsSymbolizedAsCircles() @@ -368,21 +363,43 @@ export class VectorStyle extends AbstractStyle { ); } - renderLegendDetails() { - const styles = this._getAllStyleProperties(); - const styleProperties = styles.map((style) => { - return { - // eslint-disable-next-line max-len - range: (style.isDynamic() && style.isComplete() && style.getField().getName()) ? this._getFieldRange(style.getField().getName()) : null, - style: style - }; + async _getLegendDetailStyleProperties() { + const isLinesOnly = await this._getIsLinesOnly(); + const isPolygonsOnly = await this._getIsPolygonsOnly(); + + return this.getDynamicPropertiesArray().filter(styleProperty => { + if (isLinesOnly) { + return LINE_STYLES.includes(styleProperty.getStyleName()); + } + + if (isPolygonsOnly) { + return POLYGON_STYLES.includes(styleProperty.getStyleName()); + } + + return true; }); + } - return ( - - ); + async hasLegendDetails() { + const styles = await this._getLegendDetailStyleProperties(); + return styles.length > 0; + } + + renderLegendDetails() { + const loadRows = async () => { + const styles = await this._getLegendDetailStyleProperties(); + const promises = styles.map(async (style) => { + return { + label: await style.getField().getLabel(), + fieldFormatter: await this._source.getFieldFormatter(style.getField().getName()), + range: this._getFieldRange(style.getField().getName()), + style, + }; + }); + return await Promise.all(promises); + }; + + return ; } _getStyleFields() { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index b834fb842389e5..4a0363b5cb2e19 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -27,6 +27,9 @@ export const VECTOR_STYLES = { ICON_ORIENTATION: 'iconOrientation' }; +export const LINE_STYLES = [VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; +export const POLYGON_STYLES = [VECTOR_STYLES.FILL_COLOR, VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; + export function getDefaultProperties(mapColors = []) { return { ...getDefaultStaticProperties(mapColors), diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 7e831115e6dba0..60a6201ac872cb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -145,8 +145,8 @@ export class VectorLayer extends AbstractLayer { return 'vector'; } - hasLegendDetails() { - return this._style.getDynamicPropertiesArray().length > 0; + async hasLegendDetails() { + return this._style.hasLegendDetails(); } renderLegendDetails() { From 45df5fdf4277a2094c1272c52c6cc560f297c48d Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Mon, 9 Dec 2019 15:17:29 -0600 Subject: [PATCH 21/36] [SIEM] Migrate backend to use New Platform services (#51144) * Mark incoming plugin members as readonly These cannot and should not be modifiable. * Use env var instead of EnvironmentMode * There doesn't appear to be an EnvMode in the new platform * We're only using envMode to check whether we're in production * We're already using process.env.NODE_ENV elsewhere We can revisit this, but for now I'm simplifying things under this assumption. * Pass our setup context to the compose function We're going to retrieve our router instance from this, for now. * Remove unused static files route I spent a few minutes trying to do this in the new platform, only to realize that this was cargo culted from another plugin's structure and never used. * WIP: convert main GraphQL endpoints to New Platform Splits the existing dual-method route into separate GET/POST routes, while converting it to the NP routing syntax TODO: * Full route schema declarations * Address context being moved off of the response object and into its own object; callWithRequest is currently broken for this reason. * Remove unnecesary Request type While the defaultIndex patterns can be retrieved on the request itself, that requires this special case of our FrameworkRequest. In my smoke testing, the incoming `indices` argument was never different from the one present on the request payload. Xavier had mentioned that these might be redundant and a relic of some quick prototyping, so I'm going to simplify this logic and delete that type under this assumption. * Retrieve Elasticsearch client from RequestHandlerContext In order to minimize the amount of noise on this refactor, I'm adding the RequestHandlerContext to the existing FrameworkRequest object that we already pass around. This also removes some adapter methods that were cribbed from infra but have since become unused. There are likely more. * Use uiSettings client from RequestHandlerContext Pulls from the new platform instead of from request.server. * Remove unused properties from RequestFacade One of these was obviated by the refactor to NP routing; the other may never have been necessary. * Remove unused interface This is a relic that is no longer used in the codebase. * Make error response code dynamic * Handle GraphQL errors Refactors to use new platform's responses instead of Boom. Unless we intentionally do not want isGraphQLError error headers, I saw no reason for the latter two branches of this method (and merged them). * Fix graphiQL route We needed to loosen the restriction on our main POST graphQL route, as the requests coming from graphiQL do not match our normal format. * Clean up logging * Remove unused var injection functionality I could not find a case where we were using these vars within the siem app. * Fix typo on config fetching * Migrate to NP IndexPatterns service * Removes unused extra parameter on callWithRequest * I think this was a relic from the infra code * Clean up typings of callWithRequest * GenericParams is, ironically, not generic enough to handle all ES client calls. Instead we type it as Record but ensure that our function adheres to the APICaller interface. * Use savedObjects client in request context These resolvers already receive a request containing the NP context, so we can retrieve our client directly from that, now. * Rename dependencies -> plugins to match kibana.json * Remove unnecessary type annotation The type of callCluster is already checked due to being passed to the IndexPatternsFetcher constructor. * Add siem plugin to new platform For now this just generates a config observable with some defaults; everything still lives in the legacy plugin. * WIP: flattening out plugin initialization Rather than pass our legacy API around everywhere, let's be explicit about who needs what, and start flattening things out so that we can move the legacy-independent stuff over. * Pass our plugin context to initServerWithKibana We can get the NP equivalent of `pkg.version` from context.env.packageInfo.version, so let's do that and remove a usage of config(). * Simplify siem configuration As far as I can tell, the only siem config that we're using is `xpack.siem.enabled`. The `query` was a holdover from infra, and if we're using the `sources` queries at all, it's only with the default values. Since our config is not typed, trying to add `sources` config only results in runtime errors. This removes the KibanaConfigurationAdapter entirely, and instead passes what is effectively { sources: {} } to the SourcesConfigurationAdapter. * Run all legacy-free setup through our plugin Once this is vetted, we should be able to move the entire tree under the plugin into the new platform plugin. We can inline the compose and init_server calls into the plugin once things are vetted and stable; for now leaving them there cuts down on the diff. * Temporarily ignore our unused config declaration * Fix detection engine route tests While we're passing a properly bound route function in the app, the tests' interfaces needed to be updated. Adds a helper method for retrieving a bound route function from a Server object. * Add some rudimentary schema validation to our graphQL endpoints * Remove defunct server.config fn The last remaining usage of this config was removed in #51985. * Group our dev endpoints together The graphiQL endpoint is the only thing that currently uses the GET endpoint; everything else that talks to graphQL uses POST. For that reason, I'm putting them in the same scope (along with annotating here) to make that a bit clearer. * Determine environment from plugin context The kibana platform did and does provide this interface to check with environment we're running in. * Migrate xpack_main to NP features service * Fix some issues missed in the previous merge DE added some dependencies on both the server and request objects. Most have NP equivalents and can be converted, but for now let's just add them back to the Facades and convert in another PR. Also changes one function to pull plugins from the server object, rather than the server object living on the request (as this is how similar functions are structured right now). * Fix type resulting from bad merge resolution * Fix type error due to incorrect usage of Hapi.Request Pull elasticsearch service off our legacy server object, rather than indirectly off the request object. Still legacy, but it's one less step for later. --- x-pack/legacy/plugins/siem/index.ts | 36 +-- .../plugins/siem/server/graphql/index.ts | 12 - .../server/graphql/source_status/resolvers.ts | 3 +- .../plugins/siem/server/kibana.index.ts | 82 ++---- .../plugins/siem/server/lib/compose/kibana.ts | 20 +- .../kibana_configuration_adapter.test.ts | 40 --- .../kibana_configuration_adapter.ts | 80 ------ .../routes/create_rules_route.ts | 4 +- .../routes/index/create_index_route.ts | 2 +- .../routes/index/delete_index_route.ts | 2 +- .../routes/index/read_index_route.ts | 2 +- .../signals/open_close_signals_route.ts | 2 +- .../lib/detection_engine/routes/utils.ts | 4 +- .../lib/events/elasticsearch_adapter.test.ts | 2 - .../lib/framework/kibana_framework_adapter.ts | 235 ++++++++++-------- .../siem/server/lib/framework/types.ts | 31 +-- .../lib/hosts/elasticsearch_adapter.test.ts | 6 - .../lib/index_fields/elasticsearch_adapter.ts | 12 +- .../siem/server/lib/index_fields/index.ts | 8 +- .../siem/server/lib/index_fields/types.ts | 13 +- .../kpi_hosts/elasticsearch_adapter.test.ts | 4 - .../lib/kpi_network/elastic_adapter.test.ts | 2 - .../lib/network/elastic_adapter.test.ts | 10 - .../siem/server/lib/note/saved_object.ts | 73 ++---- .../lib/overview/elastic_adapter.test.ts | 8 - .../server/lib/pinned_event/saved_object.ts | 60 ++--- .../source_status/elasticsearch_adapter.ts | 39 +-- .../siem/server/lib/sources/configuration.ts | 7 +- .../siem/server/lib/timeline/saved_object.ts | 77 ++---- .../lib/tls/elasticsearch_adapter.test.ts | 2 - .../legacy/plugins/siem/server/lib/types.ts | 14 +- x-pack/legacy/plugins/siem/server/plugin.ts | 72 ++++-- x-pack/legacy/plugins/siem/server/types.ts | 10 +- x-pack/plugins/siem/kibana.json | 8 + x-pack/plugins/siem/server/config.ts | 20 ++ x-pack/plugins/siem/server/index.ts | 17 ++ x-pack/plugins/siem/server/plugin.ts | 37 +++ 37 files changed, 402 insertions(+), 654 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts create mode 100644 x-pack/plugins/siem/kibana.json create mode 100644 x-pack/plugins/siem/server/config.ts create mode 100644 x-pack/plugins/siem/server/index.ts create mode 100644 x-pack/plugins/siem/server/plugin.ts diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index fca4a13db8cb5f..cb72fb4cfba464 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -29,6 +29,7 @@ import { DEFAULT_SIGNALS_INDEX_KEY, } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; +import { initServerWithKibana } from './server/kibana.index'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const siem = (kibana: any) => { @@ -136,43 +137,24 @@ export const siem = (kibana: any) => { mappings: savedObjectMappings, }, init(server: Server) { - const { - config, - getInjectedUiAppVars, - indexPatternsServiceFactory, - injectUiAppVars, - newPlatform, - plugins, - route, - savedObjects, - } = server; - - const { - env, - coreContext: { logger }, - setup, - } = newPlatform; - const initializerContext = { logger, env }; + const { config, newPlatform, plugins, route } = server; + const { coreContext, env, setup } = newPlatform; + const initializerContext = { ...coreContext, env } as PluginInitializerContext; const serverFacade = { config, - getInjectedUiAppVars, - indexPatternsServiceFactory, - injectUiAppVars, plugins: { alerting: plugins.alerting, - xpack_main: plugins.xpack_main, + elasticsearch: plugins.elasticsearch, spaces: plugins.spaces, }, route: route.bind(server), - savedObjects, }; - plugin(initializerContext as PluginInitializerContext).setup( - setup.core, - setup.plugins, - serverFacade - ); + // @ts-ignore-next-line: setup.plugins is too loosely typed + plugin(initializerContext).setup(setup.core, setup.plugins); + + initServerWithKibana(initializerContext, serverFacade); }, config(Joi: Root) { return Joi.object() diff --git a/x-pack/legacy/plugins/siem/server/graphql/index.ts b/x-pack/legacy/plugins/siem/server/graphql/index.ts index 901d27295479a2..4e28605f6c0b21 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/index.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/index.ts @@ -55,15 +55,3 @@ export const schemas = [ uncommonProcessesSchema, whoAmISchema, ]; - -// The types from graphql-tools/src/mock.ts 'any' based. I add slightly -// stricter types here, but these should go away when graphql-tools using something -// other than "any" in the future for its types. -// https://github.com/apollographql/graphql-tools/blob/master/src/mock.ts#L406 -export interface SiemContext { - req: { - payload: { - operationName: string; - }; - }; -} diff --git a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts index 790b3aaa830955..24589822f0250e 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts @@ -9,7 +9,6 @@ import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { IndexFields } from '../../lib/index_fields'; import { SourceStatus } from '../../lib/source_status'; import { QuerySourceResolver } from '../sources/resolvers'; -import { FrameworkFieldsRequest } from '../../lib/index_fields/types'; export type SourceStatusIndicesExistResolver = ChildResolverOf< AppResolverOf, @@ -47,7 +46,7 @@ export const createSourceStatusResolvers = (libs: { ) { return []; } - return libs.fields.getFields(req as FrameworkFieldsRequest, args.defaultIndex); + return libs.fields.getFields(req, args.defaultIndex); }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index d6db1c4ef5bb99..bb0958b32fa190 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -4,16 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; - -import { Logger, EnvironmentMode } from 'src/core/server'; -import { initServer } from './init_server'; -import { compose } from './lib/compose/kibana'; -import { - noteSavedObjectType, - pinnedEventSavedObjectType, - timelineSavedObjectType, -} from './saved_objects'; +import { PluginInitializerContext } from 'src/core/server'; import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type'; import { isAlertExecutor } from './lib/detection_engine/alerts/types'; @@ -30,74 +21,33 @@ import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_ind const APP_ID = 'siem'; -export const initServerWithKibana = ( - kbnServer: ServerFacade, - logger: Logger, - mode: EnvironmentMode -) => { - if (kbnServer.plugins.alerting != null) { - const version = kbnServer.config().get('pkg.version'); +export const initServerWithKibana = (context: PluginInitializerContext, __legacy: ServerFacade) => { + const logger = context.logger.get('plugins', APP_ID); + const version = context.env.packageInfo.version; + + if (__legacy.plugins.alerting != null) { const type = rulesAlertType({ logger, version }); if (isAlertExecutor(type)) { - kbnServer.plugins.alerting.setup.registerType(type); + __legacy.plugins.alerting.setup.registerType(type); } } - kbnServer.injectUiAppVars('siem', async () => kbnServer.getInjectedUiAppVars('kibana')); - - const libs = compose(kbnServer, mode); - initServer(libs); // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules // All REST rule creation, deletion, updating, etc... - createRulesRoute(kbnServer); - readRulesRoute(kbnServer); - updateRulesRoute(kbnServer); - deleteRulesRoute(kbnServer); - findRulesRoute(kbnServer); + createRulesRoute(__legacy); + readRulesRoute(__legacy); + updateRulesRoute(__legacy); + deleteRulesRoute(__legacy); + findRulesRoute(__legacy); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status // Example usage can be found in siem/server/lib/detection_engine/scripts/signals - setSignalsStatusRoute(kbnServer); + setSignalsStatusRoute(__legacy); // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces - createIndexRoute(kbnServer); - readIndexRoute(kbnServer); - deleteIndexRoute(kbnServer); - - const xpackMainPlugin = kbnServer.plugins.xpack_main; - xpackMainPlugin.registerFeature({ - id: APP_ID, - name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { - defaultMessage: 'SIEM', - }), - icon: 'securityAnalyticsApp', - navLinkId: 'siem', - app: ['siem', 'kibana'], - catalogue: ['siem'], - privileges: { - all: { - api: ['siem'], - savedObject: { - all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType], - read: ['config'], - }, - ui: ['show'], - }, - read: { - api: ['siem'], - savedObject: { - all: [], - read: [ - 'config', - noteSavedObjectType, - pinnedEventSavedObjectType, - timelineSavedObjectType, - ], - }, - ui: ['show'], - }, - }, - }); + createIndexRoute(__legacy); + readIndexRoute(__legacy); + deleteIndexRoute(__legacy); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts index 6e0c5e98206e4e..9d5ac6db7bbb79 100644 --- a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EnvironmentMode } from 'src/core/server'; -import { ServerFacade } from '../../types'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { Anomalies } from '../anomalies'; import { ElasticsearchAnomaliesAdapter } from '../anomalies/elasticsearch_adapter'; import { Authentications } from '../authentications'; import { ElasticsearchAuthenticationAdapter } from '../authentications/elasticsearch_adapter'; -import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter'; import { ElasticsearchEventsAdapter, Events } from '../events'; import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter'; import { ElasticsearchHostsAdapter, Hosts } from '../hosts'; @@ -28,21 +26,20 @@ import { Overview } from '../overview'; import { ElasticsearchOverviewAdapter } from '../overview/elasticsearch_adapter'; import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status'; import { ConfigurationSourcesAdapter, Sources } from '../sources'; -import { AppBackendLibs, AppDomainLibs, Configuration } from '../types'; +import { AppBackendLibs, AppDomainLibs } from '../types'; import { ElasticsearchUncommonProcessesAdapter, UncommonProcesses } from '../uncommon_processes'; import { Note } from '../note/saved_object'; import { PinnedEvent } from '../pinned_event/saved_object'; import { Timeline } from '../timeline/saved_object'; -export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackendLibs { - const configuration = new KibanaConfigurationAdapter(server); - const framework = new KibanaBackendFrameworkAdapter(server, mode); - const sources = new Sources(new ConfigurationSourcesAdapter(configuration)); +export function compose(core: CoreSetup, env: PluginInitializerContext['env']): AppBackendLibs { + const framework = new KibanaBackendFrameworkAdapter(core, env); + const sources = new Sources(new ConfigurationSourcesAdapter()); const sourceStatus = new SourceStatus(new ElasticsearchSourceStatusAdapter(framework)); - const timeline = new Timeline({ savedObjects: framework.getSavedObjectsService() }); - const note = new Note({ savedObjects: framework.getSavedObjectsService() }); - const pinnedEvent = new PinnedEvent({ savedObjects: framework.getSavedObjectsService() }); + const timeline = new Timeline(); + const note = new Note(); + const pinnedEvent = new PinnedEvent(); const domainLibs: AppDomainLibs = { anomalies: new Anomalies(new ElasticsearchAnomaliesAdapter(framework)), @@ -60,7 +57,6 @@ export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackend }; const libs: AppBackendLibs = { - configuration, framework, sourceStatus, sources, diff --git a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts deleted file mode 100644 index 153fc032883ff4..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts +++ /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 { KibanaConfigurationAdapter } from './kibana_configuration_adapter'; - -describe('the KibanaConfigurationAdapter', () => { - test('queries the xpack.siem configuration of the server', async () => { - const mockConfig = { - get: jest.fn(), - }; - - const configurationAdapter = new KibanaConfigurationAdapter({ - config: () => mockConfig, - }); - - await configurationAdapter.get(); - - expect(mockConfig.get).toBeCalledWith('xpack.siem'); - }); - - test('applies the query defaults', async () => { - const configurationAdapter = new KibanaConfigurationAdapter({ - config: () => ({ - get: () => ({}), - }), - }); - - const configuration = await configurationAdapter.get(); - - expect(configuration).toMatchObject({ - query: { - partitionSize: expect.any(Number), - partitionFactor: expect.any(Number), - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts deleted file mode 100644 index 83ddbf5cef0e46..00000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts +++ /dev/null @@ -1,80 +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 Joi from 'joi'; - -import { ConfigurationAdapter } from './adapter_types'; - -export class KibanaConfigurationAdapter - implements ConfigurationAdapter { - private readonly server: ServerWithConfig; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(server: any) { - if (!isServerWithConfig(server)) { - throw new Error('Failed to find configuration on server.'); - } - - this.server = server; - } - - public async get() { - const config = this.server.config(); - - if (!isKibanaConfiguration(config)) { - throw new Error('Failed to access configuration of server.'); - } - - const configuration = config.get('xpack.siem') || {}; - const configurationWithDefaults = { - enabled: true, - query: { - partitionSize: 75, - partitionFactor: 1.2, - ...(configuration.query || {}), - }, - sources: {}, - ...configuration, - } as Configuration; - - // we assume this to be the configuration because Kibana would have already validated it - return configurationWithDefaults; - } -} - -interface ServerWithConfig { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - config(): any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig { - return ( - Joi.validate( - maybeServer, - Joi.object({ - config: Joi.func().required(), - }).unknown() - ).error === null - ); -} - -interface KibanaConfiguration { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(key: string): any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration { - return ( - Joi.validate( - maybeConfiguration, - Joi.object({ - get: Joi.func().required(), - }).unknown() - ).error === null - ); -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts index e14c889934537b..a137d54250189c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts @@ -67,7 +67,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = try { const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, finalIndex); if (!indexExists) { return new Boom( @@ -118,6 +118,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = }; }; -export const createRulesRoute = (server: ServerFacade) => { +export const createRulesRoute = (server: ServerFacade): void => { server.route(createCreateRulesRoute(server)); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts index 619c2eccf3d99f..94c42664c281d0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts @@ -34,7 +34,7 @@ export const createCreateIndexRoute = (server: ServerFacade): Hapi.ServerRoute = async handler(request: RequestFacade) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { return new Boom(`index: "${index}" already exists`, { statusCode: 409 }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts index ca2905320d5b64..82fe0f55215fbd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -42,7 +42,7 @@ export const createDeleteIndexRoute = (server: ServerFacade): Hapi.ServerRoute = async handler(request: RequestFacade) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (!indexExists) { return new Boom(`index: "${index}" does not exist`, { statusCode: 404 }); 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 9d5aa07a330204..a8c4b7407c4487 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 @@ -27,7 +27,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute => async handler(request: RequestFacade, headers) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { // head request is used for if you want to get if the index exists 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 b4152256fbb532..99af43ce51a129 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 @@ -27,7 +27,7 @@ export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute async handler(request: SignalsRequest, headers) { const { signal_ids: signalIds, query, status } = request.payload; const index = getIndex(request, server); - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); let queryObject; if (signalIds) { queryObject = { ids: { values: signalIds } }; 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 88f072f38b7e1d..0d15fa1faa78f9 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 @@ -96,8 +96,8 @@ export const getIndex = (request: RequestFacade, server: ServerFacade): string = return `${signalsIndex}-${spaceId}`; }; -export const callWithRequestFactory = (request: RequestFacade) => { - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); +export const callWithRequestFactory = (request: RequestFacade, server: ServerFacade) => { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); return (endpoint: string, params: T, options?: U) => { return callWithRequest(request, endpoint, params, options); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts index dbeba598639b19..b1f0c3c4a3a18c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts @@ -521,10 +521,8 @@ describe('events elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts index 88c46b18d515ac..38fec7fc05a3c1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts @@ -4,14 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GenericParams } from 'elasticsearch'; import * as GraphiQL from 'apollo-server-module-graphiql'; -import Boom from 'boom'; -import { ResponseToolkit } from 'hapi'; -import { EnvironmentMode } from 'kibana/public'; import { GraphQLSchema } from 'graphql'; import { runHttpQuery } from 'apollo-server-core'; -import { ServerFacade, RequestFacade } from '../../types'; +import { schema as configSchema } from '@kbn/config-schema'; +import { + CoreSetup, + IRouter, + KibanaResponseFactory, + RequestHandlerContext, + PluginInitializerContext, +} from 'src/core/server'; +import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server'; +import { RequestFacade } from '../../types'; import { FrameworkAdapter, @@ -21,125 +26,119 @@ import { WrappableRequest, } from './types'; -interface CallWithRequestParams extends GenericParams { - max_concurrent_shard_requests?: number; -} - export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { public version: string; - public envMode: EnvironmentMode; + private isProductionMode: boolean; + private router: IRouter; - constructor(private server: ServerFacade, mode: EnvironmentMode) { - this.version = server.config().get('pkg.version'); - this.envMode = mode; + constructor(core: CoreSetup, env: PluginInitializerContext['env']) { + this.version = env.packageInfo.version; + this.isProductionMode = env.mode.prod; + this.router = core.http.createRouter(); } public async callWithRequest( req: FrameworkRequest, endpoint: string, - params: CallWithRequestParams, // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...rest: any[] + params: Record ) { - const internalRequest = req[internalFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const includeFrozen = await internalRequest.getUiSettingsService().get('search:includeFrozen'); + const { elasticsearch, uiSettings } = req.context.core; + const includeFrozen = await uiSettings.client.get('search:includeFrozen'); const maxConcurrentShardRequests = endpoint === 'msearch' - ? await internalRequest.getUiSettingsService().get('courier:maxConcurrentShardRequests') + ? await uiSettings.client.get('courier:maxConcurrentShardRequests') : 0; - const fields = await callWithRequest( - internalRequest, - endpoint, - { - ...params, - ignore_throttled: !includeFrozen, - ...(maxConcurrentShardRequests > 0 - ? { max_concurrent_shard_requests: maxConcurrentShardRequests } - : {}), - }, - ...rest - ); - return fields; - } - public exposeStaticDir(urlPath: string, dir: string): void { - this.server.route({ - handler: { - directory: { - path: dir, - }, - }, - method: 'GET', - path: urlPath, + return elasticsearch.dataClient.callAsCurrentUser(endpoint, { + ...params, + ignore_throttled: !includeFrozen, + ...(maxConcurrentShardRequests > 0 + ? { max_concurrent_shard_requests: maxConcurrentShardRequests } + : {}), }); } public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { - this.server.route({ - options: { - tags: ['access:siem'], + this.router.post( + { + path: routePath, + validate: { + body: configSchema.object({ + operationName: configSchema.string(), + query: configSchema.string(), + variables: configSchema.object({}, { allowUnknowns: true }), + }), + }, + options: { + tags: ['access:siem'], + }, }, - handler: async (request: RequestFacade, h: ResponseToolkit) => { + async (context, request, response) => { try { - const query = - request.method === 'post' - ? (request.payload as Record) // eslint-disable-line @typescript-eslint/no-explicit-any - : (request.query as Record); // eslint-disable-line @typescript-eslint/no-explicit-any - const gqlResponse = await runHttpQuery([request], { - method: request.method.toUpperCase(), + method: 'POST', options: (req: RequestFacade) => ({ - context: { req: wrapRequest(req) }, + context: { req: wrapRequest(req, context) }, schema, }), - - query, + query: request.body, }); - return h.response(gqlResponse).type('application/json'); + return response.ok({ + body: gqlResponse, + headers: { + 'content-type': 'application/json', + }, + }); } catch (error) { - if ('HttpQueryError' !== error.name) { - const queryError = Boom.boomify(error); - - queryError.output.payload.message = error.message; - - return queryError; - } - - if (error.isGraphQLError === true) { - return h - .response(error.message) - .code(error.statusCode) - .type('application/json'); - } + return this.handleError(error, response); + } + } + ); - const genericError = new Boom(error.message, { statusCode: error.statusCode }); + if (!this.isProductionMode) { + this.router.get( + { + path: routePath, + validate: { query: configSchema.object({}, { allowUnknowns: true }) }, + options: { + tags: ['access:siem'], + }, + }, + async (context, request, response) => { + try { + const { query } = request; + const gqlResponse = await runHttpQuery([request], { + method: 'GET', + options: (req: RequestFacade) => ({ + context: { req: wrapRequest(req, context) }, + schema, + }), + query, + }); - if (error.headers) { - Object.keys(error.headers).forEach(header => { - genericError.output.headers[header] = error.headers[header]; + return response.ok({ + body: gqlResponse, + headers: { + 'content-type': 'application/json', + }, }); + } catch (error) { + return this.handleError(error, response); } - - // Boom hides the error when status code is 500 - genericError.output.payload.message = error.message; - - throw genericError; } - }, - method: ['GET', 'POST'], - path: routePath, - vhost: undefined, - }); - - if (!this.envMode.prod) { - this.server.route({ - options: { - tags: ['access:siem'], + ); + + this.router.get( + { + path: `${routePath}/graphiql`, + validate: false, + options: { + tags: ['access:siem'], + }, }, - handler: async (request: RequestFacade, h: ResponseToolkit) => { + async (context, request, response) => { const graphiqlString = await GraphiQL.resolveGraphiQLString( request.query, { @@ -149,42 +148,60 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { request ); - return h.response(graphiqlString).type('text/html'); + return response.ok({ + body: graphiqlString, + headers: { + 'content-type': 'text/html', + }, + }); + } + ); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private handleError(error: any, response: KibanaResponseFactory) { + if (error.name !== 'HttpQueryError') { + return response.internalError({ + body: error.message, + headers: { + 'content-type': 'application/json', }, - method: 'GET', - path: `${routePath}/graphiql`, }); } - } - public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService { - return this.server.indexPatternsServiceFactory({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callCluster: async (method: string, args: [GenericParams], ...rest: any[]) => { - const fieldCaps = await this.callWithRequest( - request, - method, - { ...args, allowNoIndices: true } as GenericParams, - ...rest - ); - return fieldCaps; + return response.customError({ + statusCode: error.statusCode, + body: error.message, + headers: { + 'content-type': 'application/json', + ...error.headers, }, }); } - public getSavedObjectsService() { - return this.server.savedObjects; + public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callCluster = async (endpoint: string, params?: Record) => + this.callWithRequest(request, endpoint, { + ...params, + allowNoIndices: true, + }); + + return new IndexPatternsFetcher(callCluster); } } export function wrapRequest( - req: InternalRequest + req: InternalRequest, + context: RequestHandlerContext ): FrameworkRequest { const { auth, params, payload, query } = req; return { [internalFrameworkRequest]: req, auth, + context, params, payload, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts index ffa113a18c36c0..dd31ed9fcaf5fa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts @@ -7,8 +7,8 @@ import { IndicesGetMappingParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; +import { RequestHandlerContext } from 'src/core/server'; import { ESQuery } from '../../../common/typed_json'; import { PaginationInput, @@ -25,7 +25,6 @@ export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); export interface FrameworkAdapter { version: string; - exposeStaticDir(urlPath: string, dir: string): void; registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void; callWithRequest( req: FrameworkRequest, @@ -37,27 +36,17 @@ export interface FrameworkAdapter { method: 'msearch', options?: object ): Promise>; - callWithRequest( - req: FrameworkRequest, - method: 'indices.existsAlias', - options?: object - ): Promise; callWithRequest( req: FrameworkRequest, method: 'indices.getMapping', options?: IndicesGetMappingParams // eslint-disable-line ): Promise; - callWithRequest( - req: FrameworkRequest, - method: 'indices.getAlias' | 'indices.get', // eslint-disable-line - options?: object - ): Promise; getIndexPatternsService(req: FrameworkRequest): FrameworkIndexPatternsService; - getSavedObjectsService(): Legacy.SavedObjectsService; } export interface FrameworkRequest { [internalFrameworkRequest]: InternalRequest; + context: RequestHandlerContext; payload: InternalRequest['payload']; params: InternalRequest['params']; query: InternalRequest['query']; @@ -132,22 +121,6 @@ export interface FrameworkIndexPatternsService { }): Promise; } -interface Alias { - settings: { - index: { - uuid: string; - }; - }; -} - -export interface DatabaseGetIndicesResponse { - [indexName: string]: { - aliases: { - [aliasName: string]: Alias; - }; - }; -} - export interface RequestBasicOptions { sourceConfiguration: SourceConfiguration; timerange: TimerangeInput; diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts index fc5c037dfbb5ff..0d698f1e19213a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts @@ -161,10 +161,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); @@ -184,10 +182,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); @@ -207,10 +203,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts index 291345b6befd3b..48b9750ddd9499 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts @@ -14,22 +14,20 @@ import { hasDocumentation, IndexAlias, } from '../../utils/beat_schema'; -import { FrameworkAdapter } from '../framework'; -import { FieldsAdapter, IndexFieldDescriptor, FrameworkFieldsRequest } from './types'; +import { FrameworkAdapter, FrameworkRequest } from '../framework'; +import { FieldsAdapter, IndexFieldDescriptor } from './types'; type IndexesAliasIndices = Record; export class ElasticsearchIndexFieldAdapter implements FieldsAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexFields( - request: FrameworkFieldsRequest, - indices: string[] - ): Promise { + public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise { const indexPatternsService = this.framework.getIndexPatternsService(request); const indexesAliasIndices: IndexesAliasIndices = indices.reduce( (accumulator: IndexesAliasIndices, indice: string) => { - const key: string = getIndexAlias(request.payload.variables.defaultIndex, indice); + const key = getIndexAlias(indices, indice); + if (get(key, accumulator)) { accumulator[key] = [...accumulator[key], indice]; } else { diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts index 325aaa4fa4339e..a3ea8548bddc2c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts @@ -6,16 +6,14 @@ import { IndexField } from '../../graphql/types'; -import { FieldsAdapter, FrameworkFieldsRequest } from './types'; +import { FieldsAdapter } from './types'; +import { FrameworkRequest } from '../framework'; export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter'; export class IndexFields { constructor(private readonly adapter: FieldsAdapter) {} - public async getFields( - request: FrameworkFieldsRequest, - defaultIndex: string[] - ): Promise { + public async getFields(request: FrameworkRequest, defaultIndex: string[]): Promise { return this.adapter.getIndexFields(request, defaultIndex); } } diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts index 719b9e8d6e18ed..0c894c6980a31e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts @@ -6,20 +6,9 @@ import { IndexField } from '../../graphql/types'; import { FrameworkRequest } from '../framework'; -import { RequestFacade } from '../../types'; - -type IndexFieldsRequest = RequestFacade & { - payload: { - variables: { - defaultIndex: string[]; - }; - }; -}; - -export type FrameworkFieldsRequest = FrameworkRequest; export interface FieldsAdapter { - getIndexFields(req: FrameworkFieldsRequest, indices: string[]): Promise; + getIndexFields(req: FrameworkRequest, indices: string[]): Promise; } export interface IndexFieldDescriptor { diff --git a/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts index 1c388a1808c708..4a179073852b0e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts @@ -55,10 +55,8 @@ describe('getKpiHosts', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiHosts: ElasticsearchKpiHostsAdapter; @@ -171,10 +169,8 @@ describe('getKpiHostDetails', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiHosts: ElasticsearchKpiHostsAdapter; diff --git a/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts index 9c192eddf6f8a1..11d007f591fac9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts @@ -50,10 +50,8 @@ describe('Network Kpi elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiNetwork: ElasticsearchKpiNetworkAdapter; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts index 542a2a0108a9aa..eeea4bec2fb255 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts @@ -37,9 +37,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ @@ -65,10 +63,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -107,9 +103,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ @@ -140,10 +134,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -165,9 +157,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts index 30e9ace2c0f19b..e5fce811e560f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts @@ -6,7 +6,6 @@ import { failure } from 'io-ts/lib/PathReporter'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import uuid from 'uuid'; @@ -15,7 +14,6 @@ import { SavedObjectsFindOptions } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { Pick3 } from '../../../common/utility_types'; import { PageInfoNote, ResponseNote, @@ -31,20 +29,11 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; export class Note { - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) {} - public async deleteNote(request: FrameworkRequest, noteIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( - noteIds.map(noteId => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(noteSavedObjectType, noteId) - ) + noteIds.map(noteId => savedObjectsClient.delete(noteSavedObjectType, noteId)) ); } @@ -55,11 +44,11 @@ export class Note { searchFields: ['timelineId'], }; const notesToBeDeleted = await this.getAllSavedNote(request, options); + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( notesToBeDeleted.notes.map(note => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(noteSavedObjectType, note.noteId) + savedObjectsClient.delete(noteSavedObjectType, note.noteId) ) ); } @@ -119,17 +108,17 @@ export class Note { note: SavedNote ): Promise { try { + const savedObjectsClient = request.context.core.savedObjects.client; + if (noteId == null) { const timelineVersionSavedObject = note.timelineId == null ? await (async () => { const timelineResult = convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + ) ); note.timelineId = timelineResult.savedObjectId; return timelineResult.version; @@ -141,12 +130,10 @@ export class Note { code: 200, message: 'success', note: convertSavedObjectToSavedNote( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - noteSavedObjectType, - pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null) - ), + await savedObjectsClient.create( + noteSavedObjectType, + pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null) + ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ), }; @@ -157,16 +144,14 @@ export class Note { code: 200, message: 'success', note: convertSavedObjectToSavedNote( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .update( - noteSavedObjectType, - noteId, - pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null), - { - version: version || undefined, - } - ) + await savedObjectsClient.update( + noteSavedObjectType, + noteId, + pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null), + { + version: version || undefined, + } + ) ), }; } catch (err) { @@ -189,20 +174,14 @@ export class Note { } private async getSavedNote(request: FrameworkRequest, NoteId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(noteSavedObjectType, NoteId); return convertSavedObjectToSavedNote(savedObject); } private async getAllSavedNote(request: FrameworkRequest, options: SavedObjectsFindOptions) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObjects = await savedObjectsClient.find(options); return { diff --git a/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts index 75c460f0807b48..d904219c76531c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts @@ -38,10 +38,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -74,10 +72,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -114,10 +110,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -155,10 +149,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts index 882a139eb739a5..177eb05d8539d7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts @@ -6,7 +6,6 @@ import { failure } from 'io-ts/lib/PathReporter'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from 'src/core/server'; @@ -14,7 +13,6 @@ import { SavedObjectsFindOptions } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { Pick3 } from '../../../common/utility_types'; import { FrameworkRequest, internalFrameworkRequest } from '../framework'; import { PinnedEventSavedObject, @@ -27,24 +25,18 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; export class PinnedEvent { - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) {} - public async deletePinnedEventOnTimeline(request: FrameworkRequest, pinnedEventIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( pinnedEventIds.map(pinnedEventId => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(pinnedEventSavedObjectType, pinnedEventId) + savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEventId) ) ); } public async deleteAllPinnedEventsOnTimeline(request: FrameworkRequest, timelineId: string) { + const savedObjectsClient = request.context.core.savedObjects.client; const options: SavedObjectsFindOptions = { type: pinnedEventSavedObjectType, search: timelineId, @@ -53,9 +45,7 @@ export class PinnedEvent { const pinnedEventToBeDeleted = await this.getAllSavedPinnedEvents(request, options); await Promise.all( pinnedEventToBeDeleted.map(pinnedEvent => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId) + savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId) ) ); } @@ -103,18 +93,18 @@ export class PinnedEvent { eventId: string, timelineId: string | null ): Promise { + const savedObjectsClient = request.context.core.savedObjects.client; + try { if (pinnedEventId == null) { const timelineVersionSavedObject = timelineId == null ? await (async () => { const timelineResult = convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + ) ); timelineId = timelineResult.savedObjectId; // eslint-disable-line no-param-reassign return timelineResult.version; @@ -133,16 +123,14 @@ export class PinnedEvent { }; // create Pinned Event on Timeline return convertSavedObjectToSavedPinnedEvent( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - pinnedEventSavedObjectType, - pickSavedPinnedEvent( - pinnedEventId, - savedPinnedEvent, - request[internalFrameworkRequest].auth || null - ) - ), + await savedObjectsClient.create( + pinnedEventSavedObjectType, + pickSavedPinnedEvent( + pinnedEventId, + savedPinnedEvent, + request[internalFrameworkRequest].auth || null + ) + ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ); } @@ -177,10 +165,7 @@ export class PinnedEvent { } private async getSavedPinnedEvent(request: FrameworkRequest, pinnedEventId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(pinnedEventSavedObjectType, pinnedEventId); return convertSavedObjectToSavedPinnedEvent(savedObject); @@ -190,10 +175,7 @@ export class PinnedEvent { request: FrameworkRequest, options: SavedObjectsFindOptions ) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObjects = await savedObjectsClient.find(options); return savedObjects.saved_objects.map(savedObject => diff --git a/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts index 3aeb137688594f..17d203918d8254 100644 --- a/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts @@ -4,38 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DatabaseGetIndicesResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; - +import { FrameworkAdapter, FrameworkRequest } from '../framework'; import { SourceStatusAdapter } from './index'; export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexNames(request: FrameworkRequest, aliasName: string | string[]) { - const indexMaps = await Promise.all([ - this.framework - .callWithRequest(request, 'indices.getAlias', { - name: aliasName, - filterPath: '*.settings.index.uuid', // to keep the response size as small as possible - }) - .catch(withDefaultIfNotFound({})), - this.framework - .callWithRequest(request, 'indices.get', { - index: aliasName, - filterPath: '*.settings.index.uuid', // to keep the response size as small as possible - }) - .catch(withDefaultIfNotFound({})), - ]); - return indexMaps.reduce( - (indexNames, indexMap) => [...indexNames, ...Object.keys(indexMap)], - [] as string[] - ); - } - - public async hasAlias(request: FrameworkRequest, aliasName: string): Promise { - return this.framework.callWithRequest(request, 'indices.existsAlias', { - name: aliasName, - }); - } public async hasIndices(request: FrameworkRequest, indexNames: string | string[]) { return this.framework @@ -56,13 +29,3 @@ export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter { ); } } - -const withDefaultIfNotFound = (defaultValue: DefaultValue) => ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error: any -): DefaultValue => { - if (error && error.status === 404) { - return defaultValue; - } - throw error; -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts b/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts index 42ff7cca199644..aa620f6cf2590c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts +++ b/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { ConfigurationAdapter } from '../configuration'; +import { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter'; import { SourcesAdapter, SourceConfiguration } from './index'; import { PartialSourceConfigurations } from './types'; @@ -15,7 +16,11 @@ interface ConfigurationWithSources { export class ConfigurationSourcesAdapter implements SourcesAdapter { private readonly configuration: ConfigurationAdapter; - constructor(configuration: ConfigurationAdapter) { + constructor( + configuration: ConfigurationAdapter< + ConfigurationWithSources + > = new InmemoryConfigurationAdapter({ sources: {} }) + ) { this.configuration = configuration; } diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts index 515920ace28d85..04c1584f1204d5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from 'src/core/server'; -import { Pick3 } from '../../../common/utility_types'; import { ResponseTimeline, PageInfoTimeline, @@ -34,17 +32,8 @@ interface ResponseTimelines { } export class Timeline { - private readonly note: Note; - private readonly pinnedEvent: PinnedEvent; - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) { - this.note = new Note({ savedObjects: this.libs.savedObjects }); - this.pinnedEvent = new PinnedEvent({ savedObjects: this.libs.savedObjects }); - } + private readonly note = new Note(); + private readonly pinnedEvent = new PinnedEvent(); public async getTimeline( request: FrameworkRequest, @@ -149,6 +138,8 @@ export class Timeline { version: string | null, timeline: SavedTimeline ): Promise { + const savedObjectsClient = request.context.core.savedObjects.client; + try { if (timelineId == null) { // Create new timeline @@ -156,40 +147,33 @@ export class Timeline { code: 200, message: 'success', timeline: convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline( - timelineId, - timeline, - request[internalFrameworkRequest].auth || null - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline( + timelineId, + timeline, + request[internalFrameworkRequest].auth || null ) + ) ), }; } // Update Timeline - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .update( - timelineSavedObjectType, - timelineId, - pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), - { - version: version || undefined, - } - ); + await savedObjectsClient.update( + timelineSavedObjectType, + timelineId, + pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), + { + version: version || undefined, + } + ); return { code: 200, message: 'success', timeline: await this.getSavedTimeline(request, timelineId), }; } catch (err) { - if ( - timelineId != null && - this.libs.savedObjects.SavedObjectsClient.errors.isConflictError(err) - ) { + if (timelineId != null && savedObjectsClient.errors.isConflictError(err)) { return { code: 409, message: err.message, @@ -212,12 +196,12 @@ export class Timeline { } public async deleteTimeline(request: FrameworkRequest, timelineIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( timelineIds.map(timelineId => Promise.all([ - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(timelineSavedObjectType, timelineId), + savedObjectsClient.delete(timelineSavedObjectType, timelineId), this.note.deleteNoteByTimelineId(request, timelineId), this.pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), ]) @@ -226,10 +210,7 @@ export class Timeline { } private async getBasicSavedTimeline(request: FrameworkRequest, timelineId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); return convertSavedObjectToSavedTimeline(savedObject); @@ -238,10 +219,7 @@ export class Timeline { private async getSavedTimeline(request: FrameworkRequest, timelineId: string) { const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); const timelineSaveObject = convertSavedObjectToSavedTimeline(savedObject); const timelineWithNotesAndPinnedEvents = await Promise.all([ @@ -257,10 +235,7 @@ export class Timeline { private async getAllSavedTimeline(request: FrameworkRequest, options: SavedObjectsFindOptions) { const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); + const savedObjectsClient = request.context.core.savedObjects.client; if (options.searchFields != null && options.searchFields.includes('favorite.keySearch')) { options.search = `${options.search != null ? options.search : ''} ${ userName != null ? convertStringToBase64(userName) : null diff --git a/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts index 0ba0dfcb516fdc..32a5c72215dda6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts @@ -24,10 +24,8 @@ describe('elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; beforeAll(async () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index c53805dc95fe79..e97a07e276dcfb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +export { ConfigType as Configuration } from '../../../../../plugins/siem/server'; import { Anomalies } from './anomalies'; import { Authentications } from './authentications'; -import { ConfigurationAdapter } from './configuration'; import { Events } from './events'; import { FrameworkAdapter, FrameworkRequest } from './framework'; import { Hosts } from './hosts'; @@ -17,7 +17,7 @@ import { KpiNetwork } from './kpi_network'; import { Network } from './network'; import { Overview } from './overview'; import { SourceStatus } from './source_status'; -import { Sources, SourceConfiguration } from './sources'; +import { Sources } from './sources'; import { UncommonProcesses } from './uncommon_processes'; import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; @@ -43,7 +43,6 @@ export interface AppDomainLibs { } export interface AppBackendLibs extends AppDomainLibs { - configuration: ConfigurationAdapter; framework: FrameworkAdapter; sources: Sources; sourceStatus: SourceStatus; @@ -52,15 +51,6 @@ export interface AppBackendLibs extends AppDomainLibs { pinnedEvent: PinnedEvent; } -export interface Configuration { - enabled: boolean; - query: { - partitionSize: number; - partitionFactor: number; - }; - sources: Record; -} - export interface SiemContext { req: FrameworkRequest; } diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 9a799a90efc65b..533b6c23088eca 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -4,27 +4,71 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, EnvironmentMode, PluginInitializerContext, Logger } from 'src/core/server'; -import { ServerFacade } from './types'; -import { initServerWithKibana } from './kibana.index'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { PluginSetupContract as FeaturesSetupContract } from '../../../../plugins/features/server'; +import { initServer } from './init_server'; +import { compose } from './lib/compose/kibana'; +import { + noteSavedObjectType, + pinnedEventSavedObjectType, + timelineSavedObjectType, +} from './saved_objects'; + +export interface PluginsSetup { + features: FeaturesSetupContract; +} export class Plugin { - name = 'siem'; - private mode: EnvironmentMode; - private logger: Logger; + readonly name = 'siem'; + private readonly logger: Logger; + private context: PluginInitializerContext; - constructor({ env, logger }: PluginInitializerContext) { - this.logger = logger.get('plugins', this.name); - this.mode = env.mode; + constructor(context: PluginInitializerContext) { + this.context = context; + this.logger = context.logger.get('plugins', this.name); - this.logger.info('NP plugin initialized'); + this.logger.debug('Shim plugin initialized'); } - public setup(core: CoreSetup, dependencies: {}, __legacy: ServerFacade) { - this.logger.info('NP plugin setup'); + public setup(core: CoreSetup, plugins: PluginsSetup) { + this.logger.debug('Shim plugin setup'); - initServerWithKibana(__legacy, this.logger, this.mode); + plugins.features.registerFeature({ + id: this.name, + name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { + defaultMessage: 'SIEM', + }), + icon: 'securityAnalyticsApp', + navLinkId: 'siem', + app: ['siem', 'kibana'], + catalogue: ['siem'], + privileges: { + all: { + api: ['siem'], + savedObject: { + all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType], + read: ['config'], + }, + ui: ['show'], + }, + read: { + api: ['siem'], + savedObject: { + all: [], + read: [ + 'config', + noteSavedObjectType, + pinnedEventSavedObjectType, + timelineSavedObjectType, + ], + }, + ui: ['show'], + }, + }, + }); - this.logger.info('NP plugin setup complete'); + const libs = compose(core, this.context.env); + initServer(libs); } } diff --git a/x-pack/legacy/plugins/siem/server/types.ts b/x-pack/legacy/plugins/siem/server/types.ts index ad19872b7a75dc..989aaf2b6eeea3 100644 --- a/x-pack/legacy/plugins/siem/server/types.ts +++ b/x-pack/legacy/plugins/siem/server/types.ts @@ -8,29 +8,21 @@ import { Legacy } from 'kibana'; export interface ServerFacade { config: Legacy.Server['config']; - getInjectedUiAppVars: Legacy.Server['getInjectedUiAppVars']; - indexPatternsServiceFactory: Legacy.Server['indexPatternsServiceFactory']; - injectUiAppVars: Legacy.Server['injectUiAppVars']; plugins: { alerting?: Legacy.Server['plugins']['alerting']; + elasticsearch: Legacy.Server['plugins']['elasticsearch']; spaces: Legacy.Server['plugins']['spaces']; - xpack_main: Legacy.Server['plugins']['xpack_main']; }; route: Legacy.Server['route']; - savedObjects: Legacy.Server['savedObjects']; } export interface RequestFacade { auth: Legacy.Request['auth']; getAlertsClient?: Legacy.Request['getAlertsClient']; getActionsClient?: Legacy.Request['getActionsClient']; - getUiSettingsService: Legacy.Request['getUiSettingsService']; headers: Legacy.Request['headers']; method: Legacy.Request['method']; params: Legacy.Request['params']; payload: unknown; query: Legacy.Request['query']; - server: { - plugins: { elasticsearch: Legacy.Request['server']['plugins']['elasticsearch'] }; - }; } diff --git a/x-pack/plugins/siem/kibana.json b/x-pack/plugins/siem/kibana.json new file mode 100644 index 00000000000000..2bc33b87a1b438 --- /dev/null +++ b/x-pack/plugins/siem/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "siem", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "siem"], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/siem/server/config.ts b/x-pack/plugins/siem/server/config.ts new file mode 100644 index 00000000000000..8518177cf6f968 --- /dev/null +++ b/x-pack/plugins/siem/server/config.ts @@ -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 { Observable } from 'rxjs'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export const createConfig$ = (context: PluginInitializerContext) => + context.config.create>(); + +export type ConfigType = ReturnType extends Observable + ? T + : ReturnType; diff --git a/x-pack/plugins/siem/server/index.ts b/x-pack/plugins/siem/server/index.ts new file mode 100644 index 00000000000000..c675be691b47ed --- /dev/null +++ b/x-pack/plugins/siem/server/index.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 } from 'src/core/server'; +import { Plugin } from './plugin'; +import { configSchema, ConfigType } from './config'; + +export const plugin = (context: PluginInitializerContext) => { + return new Plugin(context); +}; + +export const config = { schema: configSchema }; + +export { ConfigType }; diff --git a/x-pack/plugins/siem/server/plugin.ts b/x-pack/plugins/siem/server/plugin.ts new file mode 100644 index 00000000000000..866f4d7575e2f6 --- /dev/null +++ b/x-pack/plugins/siem/server/plugin.ts @@ -0,0 +1,37 @@ +/* + * 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 { Observable } from 'rxjs'; + +import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { createConfig$, ConfigType } from './config'; + +export class Plugin { + readonly name = 'siem'; + private readonly logger: Logger; + // @ts-ignore-next-line TODO(rylnd): use it or lose it + private readonly config$: Observable; + + constructor(context: PluginInitializerContext) { + const { logger } = context; + this.logger = logger.get(); + this.logger.debug('plugin initialized'); + + this.config$ = createConfig$(context); + } + + public setup(core: CoreSetup, plugins: {}) { + this.logger.debug('plugin setup'); + } + + public start() { + this.logger.debug('plugin started'); + } + + public stop() { + this.logger.debug('plugin stopped'); + } +} From c692689f21d0e3f65c2b6a55488980d4c3142c37 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 9 Dec 2019 22:18:57 +0100 Subject: [PATCH 22/36] fix import (#52555) --- .../authorization/register_privileges_with_cluster.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts index 888565cd7e0ff9..c18c071ca76758 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient, Logger } from '../../../../../target/types/core/server'; +import { IClusterClient, Logger } from '../../../../../src/core/server'; import { RawKibanaPrivileges } from '../../common/model'; import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; From a863dca9c2b5d21c6a47becc9e1bb1fac156dee5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 9 Dec 2019 14:38:46 -0700 Subject: [PATCH 23/36] [Maps] better style defaults (#52420) * [Maps] better style defaults * rename consts --- .../es_geo_grid_source/es_geo_grid_source.js | 35 ++++++++++++------- .../es_pew_pew_source/es_pew_pew_source.js | 2 -- .../components/size/size_range_selector.js | 10 +++--- .../layers/styles/vector/vector_constants.js | 2 +- .../styles/vector/vector_style_defaults.js | 12 +++---- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index f4cb43ad90146b..de1b47ea28a910 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -25,6 +25,7 @@ import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; +import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property'; const MAX_GEOTILE_LEVEL = 29; @@ -216,14 +217,14 @@ export class ESGeoGridSource extends AbstractESAggSource { ]; } - _createDefaultLayerDescriptor(options) { - if (this._descriptor.requestType === RENDER_AS.HEATMAP) { - return HeatmapLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options - }); - } + _createHeatmapLayerDescriptor(options) { + return HeatmapLayer.createDescriptor({ + sourceDescriptor: this._descriptor, + ...options + }); + } + _createVectorLayerDescriptor(options) { const descriptor = VectorLayer.createDescriptor({ sourceDescriptor: this._descriptor, ...options @@ -244,6 +245,18 @@ export class ESGeoGridSource extends AbstractESAggSource { color: 'Blues' } }, + [VECTOR_STYLES.LINE_COLOR]: { + type: StaticStyleProperty.type, + options: { + color: '#FFF' + } + }, + [VECTOR_STYLES.LINE_WIDTH]: { + type: StaticStyleProperty.type, + options: { + size: 0 + } + }, [VECTOR_STYLES.ICON_SIZE]: { type: DynamicStyleProperty.type, options: { @@ -253,8 +266,6 @@ export class ESGeoGridSource extends AbstractESAggSource { name: COUNT_PROP_NAME, origin: SOURCE_DATA_ID_ORIGIN }, - minSize: 4, - maxSize: 32, } } }); @@ -264,15 +275,15 @@ export class ESGeoGridSource extends AbstractESAggSource { createDefaultLayer(options) { if (this._descriptor.requestType === RENDER_AS.HEATMAP) { return new HeatmapLayer({ - layerDescriptor: this._createDefaultLayerDescriptor(options), + layerDescriptor: this._createHeatmapLayerDescriptor(options), source: this }); } - const layerDescriptor = this._createDefaultLayerDescriptor(options); + const layerDescriptor = this._createVectorLayerDescriptor(options); const style = new VectorStyle(layerDescriptor.style, this); return new VectorLayer({ - layerDescriptor: layerDescriptor, + layerDescriptor, source: this, style }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 4eb0a952defba4..c897ee2a5b2d06 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -146,8 +146,6 @@ export class ESPewPewSource extends AbstractESAggSource { name: COUNT_PROP_NAME, origin: SOURCE_DATA_ID_ORIGIN }, - minSize: 4, - maxSize: 32, } } }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js index 4279fbbe0fbb35..1d5815a84920cb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js @@ -7,21 +7,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ValidatedDualRange } from 'ui/validated_range'; -import { DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE } from '../../vector_style_defaults'; +import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; import { i18n } from '@kbn/i18n'; export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { const onSizeChange = ([min, max]) => { onChange({ - minSize: Math.max(DEFAULT_MIN_SIZE, parseInt(min, 10)), - maxSize: Math.min(DEFAULT_MAX_SIZE, parseInt(max, 10)), + minSize: Math.max(MIN_SIZE, parseInt(min, 10)), + maxSize: Math.min(MAX_SIZE, parseInt(max, 10)), }); }; return ( Date: Mon, 9 Dec 2019 15:59:07 -0700 Subject: [PATCH 24/36] remove remaining idx usage (#52354) * remove remaining idx usage * handle possibly undefined value * update NOTICE.txt --- NOTICE.txt | 26 - packages/eslint-config-kibana/.eslintrc.js | 5 + packages/kbn-babel-preset/common_preset.js | 7 - packages/kbn-babel-preset/package.json | 1 - packages/kbn-elastic-idx/.npmignore | 3 - packages/kbn-elastic-idx/README.md | 76 -- packages/kbn-elastic-idx/babel/index.js | 304 -------- packages/kbn-elastic-idx/babel/index.test.js | 694 ------------------ packages/kbn-elastic-idx/package.json | 28 - packages/kbn-elastic-idx/src/index.ts | 65 -- packages/kbn-elastic-idx/tsconfig.json | 8 - .../index_patterns/index_patterns.ts | 7 +- .../infra/common/inventory_models/layouts.ts | 3 +- .../infra/common/inventory_models/toolbars.ts | 3 +- .../infra/public/hooks/use_http_request.tsx | 5 +- .../fields/framework_fields_adapter.ts | 6 +- .../infra/server/lib/snapshot/snapshot.ts | 6 +- .../data_frame_analytics/common/analytics.ts | 6 +- .../analytics_list/expanded_row.tsx | 3 +- .../use_create_analytics_form/reducer.ts | 9 +- .../use_create_analytics_form/state.test.ts | 12 +- .../use_create_analytics_form.ts | 5 +- .../common/job_creator/util/general.ts | 15 +- .../preconfigured_job_redirect.ts | 3 +- .../public/application/util/object_utils.ts | 4 +- .../transform/common/utils/object_utils.ts | 4 +- .../public/app/common/transform_stats.ts | 5 +- x-pack/package.json | 1 - 28 files changed, 43 insertions(+), 1271 deletions(-) delete mode 100644 packages/kbn-elastic-idx/.npmignore delete mode 100644 packages/kbn-elastic-idx/README.md delete mode 100644 packages/kbn-elastic-idx/babel/index.js delete mode 100644 packages/kbn-elastic-idx/babel/index.test.js delete mode 100644 packages/kbn-elastic-idx/package.json delete mode 100644 packages/kbn-elastic-idx/src/index.ts delete mode 100644 packages/kbn-elastic-idx/tsconfig.json diff --git a/NOTICE.txt b/NOTICE.txt index 989bb6f754a091..230e5117460224 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -186,32 +186,6 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -This product includes code that is based on facebookincubator/idx, which was -available under a "MIT" license. - -MIT License - -Copyright (c) 2013-present, Facebook, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - --- This product includes code that was extracted from angular@1.3. Original license: diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 98fa62021b5bbe..a7bce739ba8e3f 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -28,6 +28,11 @@ module.exports = { to: false, disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` }, + { + from: '@kbn/elastic-idx', + to: false, + disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` + }, { from: 'x-pack', toRelative: 'x-pack', diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index d1b7bc20dd9f94..0de9318ea0c274 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -35,13 +35,6 @@ const plugins = [ // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), ]; -const isTestEnv = process.env.BABEL_ENV === 'test' || process.env.NODE_ENV === 'test'; - -// Only load the idx plugin in non-test environments, since it conflicts with -// Jest's coverage mapping. -if (!isTestEnv) { - plugins.push(require.resolve('@kbn/elastic-idx/babel')); -} module.exports = { presets: [require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')], diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index 1913301e21a764..e554859928c0b7 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -12,7 +12,6 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", - "@kbn/elastic-idx": "1.0.0", "babel-plugin-add-module-exports": "^1.0.2", "babel-plugin-filter-imports": "^3.0.0", "babel-plugin-transform-define": "^1.3.1", diff --git a/packages/kbn-elastic-idx/.npmignore b/packages/kbn-elastic-idx/.npmignore deleted file mode 100644 index ece13b72c93eaa..00000000000000 --- a/packages/kbn-elastic-idx/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -/tsconfig.json -/src -/babel/index.test.js diff --git a/packages/kbn-elastic-idx/README.md b/packages/kbn-elastic-idx/README.md deleted file mode 100644 index 81cd5d0f86e9bb..00000000000000 --- a/packages/kbn-elastic-idx/README.md +++ /dev/null @@ -1,76 +0,0 @@ -Kibana elastic-idx Library -========================== - -The `@kbn/elastic-idx` package provides the `idx` function used for optional -chaining. Currently, the optional chaining draft is in stage 1, making it too -uncertain to add syntax support within Kibana. Other optional chaining -libraries require the Proxy object to be polyfilled for browser support, -however, this polyfill is not fully supported across all browsers that Kibana -requires. The facebookincubator `idx` project -(https://github.com/facebookincubator/idx) provides an answer to this with a -specific implementation that is understood by TypeScript so that type -information does not get lost (unlike lodash get) The `@kbn/elastic-idx` -library makes use the `idx` idiom but differs in the way null values within the -property chain are handled. - -Similar to the facebookincubator `idx` project, `@kbn/elastic-idx` also -provides the Babel plugin to transform `idx()` function calls into the expanded -form. This Babel plugin was based off the facebookincubator `idx` Babel -plugin, since the invocation syntax is almost identical, but the transformed -code differs to match how the `@kbn/elastic-idx` library treats null values. - -App Usage ----------- -Within Kibana, `@kbn/elastic-idx` can be imported and used in any JavaScript or -TypeScript project: - -``` -import { idx } from '@kbn/elastic-idx'; - -const obj0 = { a: { b: { c: { d: 'iamdefined' } } } }; -const obj1 = { a: { b: null } }; - -idx(obj0, _ => _.a.b.c.d); // returns 'iamdefined' -idx(obj1, _ => _.a.b.c.e); // returns undefined -idx(obj1, _ => _.a.b); // returns null -``` - -Build Optimization -------------------- -Similar to the facebookincubator `idx` project, it is NOT RECOMMENDED to use -idx in shipped app code. The implementation details which make -`@kbn/elastic-idx` possible comes at a non-negligible performance cost. This -usually isn't noticable during development, but for production builds, it is -recommended to transform idx calls into native, expanded form JS. Use the -plugin `@kbn/elastic-idx/babel` within your Babel configuration: - -``` -{ "plugins": [ "@kbn/elastic-idx/babel" ] } -``` - -The resulting Babel transforms the following: - -``` -import { idx } from '@kbn/elastic-idx'; -const obj = { a: { b: { c: { d: 'iamdefined' } } } }; -idx(obj, _ => _.a.b.c.d); -``` - -into this: - -``` -obj != null && -obj.a != null && -obj.a.b != null && -obj.a.b.c != null ? -obj.a.b.c.d : undefined -``` - -Note that this also removes the import statement from the source code, since it -no longer needs to be bundled. - -Testing --------- - -Tests can be run with `npm test`. This includes "functional" tests that -transform and evaluate idx calls. diff --git a/packages/kbn-elastic-idx/babel/index.js b/packages/kbn-elastic-idx/babel/index.js deleted file mode 100644 index 89171e3565530b..00000000000000 --- a/packages/kbn-elastic-idx/babel/index.js +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint strict: 0, new-cap: 0 */ - -'use strict'; -module.exports = context => { - const t = context.types; - - const idxRe = /\bidx\b/; - - function checkIdxArguments(file, node) { - const args = node.arguments; - if (args.length !== 2) { - throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.'); - } - const arrowFunction = args[1]; - if (!t.isArrowFunctionExpression(arrowFunction)) { - throw file.buildCodeFrameError( - arrowFunction, - 'The second argument supplied to `idx` must be an arrow function.' - ); - } - if (!t.isExpression(arrowFunction.body)) { - throw file.buildCodeFrameError( - arrowFunction.body, - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - } - if (arrowFunction.params.length !== 1) { - throw file.buildCodeFrameError( - arrowFunction.params[2] || arrowFunction, - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - } - const input = arrowFunction.params[0]; - if (!t.isIdentifier(input)) { - throw file.buildCodeFrameError( - arrowFunction.params[0], - 'The parameter supplied to `idx` must be an identifier.' - ); - } - } - - function checkIdxBindingNode(file, node) { - if (t.isImportDeclaration(node)) { - // E.g. `import '...'` - if (node.specifiers.length === 0) { - throw file.buildCodeFrameError(node, 'The idx import must have a value.'); - } - // E.g. `import A, {B} from '...'` - // `import A, * as B from '...'` - // `import {A, B} from '...'` - if (node.specifiers.length > 1) { - throw file.buildCodeFrameError( - node.specifiers[1], - 'The idx import must be a single specifier.' - ); - } - // `importKind` is not a property unless flow syntax is enabled. - // On specifiers, `importKind` is not "value" when it's not a type, it's - // `null`. - // E.g. `import type {...} from '...'` - // `import typeof {...} from '...'` - // `import {type ...} from '...'`. - // `import {typeof ...} from '...'` - if ( - node.importKind === 'type' || - node.importKind === 'typeof' || - node.specifiers[0].importKind === 'type' || - node.specifiers[0].importKind === 'typeof' - ) { - throw file.buildCodeFrameError(node, 'The idx import must be a value import.'); - } - } else if (t.isVariableDeclarator(node)) { - // E.g. var {idx} or var [idx] - if (!t.isIdentifier(node.id)) { - throw file.buildCodeFrameError( - node.specifiers[0], - 'The idx declaration must be an identifier.' - ); - } - } - } - - class UnsupportedNodeTypeError extends Error { - constructor(node, ...params) { - super(`Node type is not supported: ${node.type}`, ...params); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, UnsupportedNodeTypeError); - } - - this.name = 'UnsupportedNodeTypeError'; - } - } - - function getDeepProperties(node, properties = [], computedProperties = new Set()) { - if (t.isMemberExpression(node)) { - if (node.computed) { - computedProperties.add(node.property); - } - return getDeepProperties(node.object, [node.property, ...properties], computedProperties); - } else if (t.isIdentifier(node)) { - return [[node, ...properties], computedProperties]; - } - - throw new UnsupportedNodeTypeError(node); - } - - function buildMemberChain(properties, computedProperties) { - if (properties.length > 1) { - const lead = properties.slice(0, properties.length - 1); - const last = properties[properties.length - 1]; - return t.MemberExpression( - buildMemberChain(lead, computedProperties), - last, - computedProperties.has(last) - ); - } else if (properties.length === 1) { - return properties[0]; - } - return t.identifier('undefined'); - } - - function buildExpandedMemberNullChecks( - leadingProperties = [], - trailingProperties = [], - computedProperties - ) { - const propertyChainNullCheck = t.BinaryExpression( - '!=', - buildMemberChain(leadingProperties, computedProperties), - t.NullLiteral() - ); - - if (trailingProperties.length <= 1) { - return propertyChainNullCheck; - } - - const [headTrailingProperty, ...tailProperties] = trailingProperties; - - return t.LogicalExpression( - '&&', - propertyChainNullCheck, - buildExpandedMemberNullChecks( - [...leadingProperties, headTrailingProperty], - tailProperties, - computedProperties - ) - ); - } - - function buildExpandedMemberAccess(node, state) { - let baseNode; - let properties; - let computedProperties; - - try { - [[baseNode, ...properties], computedProperties] = getDeepProperties(node); - } catch (error) { - if (error instanceof UnsupportedNodeTypeError) { - throw state.file.buildCodeFrameError( - node, - 'idx callbacks may only access properties on the callback parameter.' - ); - } - - throw error; - } - - if (baseNode.name !== state.base.name) { - throw state.file.buildCodeFrameError( - node, - 'The parameter of the arrow function supplied to `idx` must match ' + - 'the base of the body expression.' - ); - } - return t.ConditionalExpression( - buildExpandedMemberNullChecks([state.input], properties, computedProperties), - buildMemberChain([state.input, ...properties], computedProperties), - t.identifier('undefined') - ); - } - - function visitIdxCallExpression(path, state) { - const node = path.node; - checkIdxArguments(state.file, node); - const replacement = buildExpandedMemberAccess(node.arguments[1].body, { - file: state.file, - input: node.arguments[0], - base: node.arguments[1].params[0], - }); - path.replaceWith(replacement); - } - - function isIdxImportOrRequire(node, name) { - if (t.isImportDeclaration(node)) { - if (name instanceof RegExp) { - return name.test(node.source.value); - } else { - return t.isStringLiteral(node.source, { value: name }); - } - } else if (t.isVariableDeclarator(node)) { - return ( - t.isCallExpression(node.init) && - t.isIdentifier(node.init.callee, { name: 'require' }) && - (name instanceof RegExp - ? name.test(node.init.arguments[0].value) - : t.isLiteral(node.init.arguments[0], { value: name })) - ); - } else { - return false; - } - } - - const declareVisitor = { - 'ImportDeclaration|VariableDeclarator'(path, state) { - if (!isIdxImportOrRequire(path.node, state.importName)) { - return; - } - - checkIdxBindingNode(state.file, path.node); - - const bindingName = t.isImportDeclaration(path.node) - ? path.node.specifiers[0].local.name - : path.node.id.name; - const idxBinding = path.scope.getOwnBinding(bindingName); - - idxBinding.constantViolations.forEach(refPath => { - throw state.file.buildCodeFrameError(refPath.node, '`idx` cannot be redefined.'); - }); - - let didTransform = false; - let didSkip = false; - - // Traverse the references backwards to process inner calls before - // outer calls. - idxBinding.referencePaths - .slice() - .reverse() - .forEach(refPath => { - if (refPath.node === idxBinding.node) { - // Do nothing... - } else if (refPath.parentPath.isMemberExpression()) { - visitIdxCallExpression(refPath.parentPath.parentPath, state); - didTransform = true; - } else if (refPath.parentPath.isCallExpression()) { - visitIdxCallExpression(refPath.parentPath, state); - didTransform = true; - } else { - // Should this throw? - didSkip = true; - } - }); - if (didTransform && !didSkip) { - path.remove(); - } - }, - }; - - return { - visitor: { - Program(path, state) { - const importName = state.opts.importName || '@kbn/elastic-idx'; - // If there can't reasonably be an idx call, exit fast. - if (importName !== '@kbn/elastic-idx' || idxRe.test(state.file.code)) { - // We're very strict about the shape of idx. Some transforms, like - // "babel-plugin-transform-async-to-generator", will convert arrow - // functions inside async functions into regular functions. So we do - // our transformation before any one else interferes. - const newState = { file: state.file, importName }; - path.traverse(declareVisitor, newState); - } - }, - }, - }; -}; diff --git a/packages/kbn-elastic-idx/babel/index.test.js b/packages/kbn-elastic-idx/babel/index.test.js deleted file mode 100644 index c4aee5266d6fa3..00000000000000 --- a/packages/kbn-elastic-idx/babel/index.test.js +++ /dev/null @@ -1,694 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -'use strict'; // eslint-disable-line strict - -jest.autoMockOff(); - -const { transformSync: babelTransform } = require('@babel/core'); -const babelPluginIdx = require('./index'); -const transformAsyncToGenerator = require('@babel/plugin-transform-async-to-generator'); -const vm = require('vm'); - -function transform(source, plugins, options) { - return babelTransform(source, { - plugins: plugins || [[babelPluginIdx, options]], - babelrc: false, - highlightCode: false, - }).code; -} - -const asyncToGeneratorHelperCode = ` -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } -} - -function _asyncToGenerator(fn) { - return function() { - var self = this, - args = arguments; - return new Promise(function(resolve, reject) { - var gen = fn.apply(self, args); - - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); - } - _next(undefined); - }); - }; -} -`; - -function stringByTrimmingSpaces(string) { - return string.replace(/\s+/g, ''); -} - -expect.extend({ - toTransformInto: (input, expected) => { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - const actual = transform(code, plugins, options); - const pass = stringByTrimmingSpaces(actual) === stringByTrimmingSpaces(expected); - return { - pass, - message: () => - 'Expected input to transform into:\n' + expected + '\n' + 'Instead, got:\n' + actual, - }; - }, - toThrowTransformError: (input, expected) => { - try { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - transform(code, plugins, options); - } catch (error) { - const actual = /^.+:\s*(.*)/.exec(error.message)[1]; // Strip "undefined: " and code snippet - return { - pass: actual === expected, - message: () => - 'Expected transform to throw "' + expected + '", but instead ' + 'got "' + actual + '".', - }; - } - return { - pass: false, - message: () => 'Expected transform to throw "' + expected + '".', - }; - }, - toReturn: (input, expected) => { - const code = transform(input, undefined); - const actual = vm.runInNewContext(code); - return { - pass: actual === expected, - message: () => 'Expected "' + expected + '" but got "' + actual + '".', - }; - }, -}); - -describe('kbn-babel-plugin-apm-idx', () => { - it('transforms member expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c.d.e); - `).toTransformInto(` - base != null && base.b != null && base.b.c != null && base.b.c.d != null - ? base.b.c.d.e - : undefined; - `); - }); - - it('throws on call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c(...foo)().d(bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms bracket notation', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"][0][c + d]); - `).toTransformInto(` - base != null && base["b"] != null && base["b"][0] != null ? base["b"][0][c + d] : undefined; - `); - }); - - it('throws on bracket notation call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"](...foo)()[0][c + d](bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms combination of both member access notations', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.a["b"].c[d[e[f]]].g); - `).toTransformInto(` - base != null && base.a != null && base.a["b"] != null && base.a["b"].c != null && base.a["b"].c[d[e[f]]] != null - ? base.a["b"].c[d[e[f]]].g - : undefined; - `); - }); - - it('transforms if the base is an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(this.props.base[5], _ => _.property); - `).toTransformInto(` - this.props.base[5] != null ? this.props.base[5].property : undefined; - `); - }); - - it('throws if the arrow function has more than one param', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, (a, b) => _.property); - `).toThrowTransformError( - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - }); - - it('throws if the arrow function has an invalid base', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, a => b.property) - `).toThrowTransformError( - 'The parameter of the arrow function supplied to `idx` must match the ' + - 'base of the body expression.' - ); - }); - - it('throws if the arrow function expression has non-properties/methods', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => (_.a++).b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('throws if the body of the arrow function is not an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => {}) - `).toThrowTransformError( - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - }); - - it('ignores non-function call idx', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `); - }); - - it('throws if idx is called with zero arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with one argument', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with three arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1, 2, 3); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('transforms idx calls as part of another expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - paddingStatement(); - a = idx(base, _ => _.b[c]); - `).toTransformInto(` - paddingStatement(); - a = base != null && base.b != null ? base.b[c] : undefined; - `); - }); - - it('transforms nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toTransformInto(` - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ) != null - && - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e != null ? - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e.f : - undefined; - `); - }); - - it('transforms idx calls inside async functions (plugin order #1)', () => { - expect({ - plugins: [babelPluginIdx, transformAsyncToGenerator], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls inside async functions (plugin order #2)', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls in async methods', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - class Foo { - async bar() { - idx(base, _ => _.b); - return this; - } - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - class Foo { - bar() { - var _this = this; - - return _asyncToGenerator(function* () { - base != null ? base.b : undefined; - return _this; - })(); - } - } - `); - }); - - it('transforms idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms deep idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms deep idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms idx calls when an idx is called as a member function on the binding in scope', () => { - expect(` - const elastic_idx = require("@kbn/elastic-idx"); - const result = elastic_idx.idx(base, _ => _.a.b.c.d); - `).toTransformInto(` - const result = base != null && - base.a != null && - base.a.b != null && - base.a.b.c != null ? - base.a.b.c.d : - undefined; - `); - }); - - it('throws on base call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _().b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms when the idx parent is a scope creating expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - (() => idx(base, _ => _.b)); - `).toTransformInto(` - () => base != null ? base.b : undefined; - `); - }); - - it('throws if redefined before use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx = null; - idx(base, _ => _.b); - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if redefined after use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - idx = null; - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if there is a duplicate declaration', () => { - expect(() => - transform(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - function idx() {} - `) - ).toThrow(); - }); - - it('handles sibling scopes with unique idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - base != null ? base.b : undefined; - } - `); - }); - - it('handles sibling scopes with and without idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - idx(base, _ => _.b); - } - `); - }); - - it('handles nested scopes with shadowing', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - function aaa() { - idx(base, _ => _.b); - function bbb(idx) { - idx(base, _ => _.b); - } - } - `).toTransformInto(` - base != null ? base.b : undefined; - function aaa() { - base != null ? base.b : undefined; - function bbb(idx) { - idx(base, _ => _.b); - } - } - `); - }); - - it('handles named idx import', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('throws on default plus named import', () => { - expect(` - import idx, {foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on default plus namespace import', () => { - expect(` - import idx, * as foo from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on named default plus other import', () => { - expect(` - import {default as idx, foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('unused idx import should be left alone', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - `); - }); - - it('allows configuration of the import name', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the import name', () => { - expect({ - code: ` - import { idx } from '@kbn/elastic-idx'; - import { idx as i_d_x } from 'i_d_x'; - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - it('allows configuration of the require name as a string', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('allows configuration of the require name as a RegExp', () => { - expect({ - code: ` - import { idx } from '../../common/idx'; - idx(base, _ => _.b); - `, - options: { importName: /.*idx$/ }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the require name', () => { - expect({ - code: ` - const idx = require('@kbn/elastic-idx'); - const i_d_x = require('i_d_x'); - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - const idx = require('@kbn/elastic-idx'); - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - describe('functional', () => { - it('works with only properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: 2}}}; - idx(base, _ => _.a.b.c); - `).toReturn(2); - }); - - it('works with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {}}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with null properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: null}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: {d: {e: {f: 2}}}}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(2); - }); - - it('works with nested idx calls with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: null}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(undefined); - }); - }); -}); diff --git a/packages/kbn-elastic-idx/package.json b/packages/kbn-elastic-idx/package.json deleted file mode 100644 index 9532983942d6b9..00000000000000 --- a/packages/kbn-elastic-idx/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@kbn/elastic-idx", - "version": "1.0.0", - "private": true, - "license": "Apache-2.0", - "description": "Library for optional chaining & the Babel plugin to transpile idx calls to plain, optimized JS", - "main": "target/index.js", - "types": "target/index.d.js", - "repository": { - "type": "git", - "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-elastic-idx" - }, - "scripts": { - "build": "tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch", - "test": "jest" - }, - "devDependencies": { - "@babel/core": "^7.5.5", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "jest": "^24.9.0", - "typescript": "3.7.2" - }, - "jest": { - "testEnvironment": "node" - } -} diff --git a/packages/kbn-elastic-idx/src/index.ts b/packages/kbn-elastic-idx/src/index.ts deleted file mode 100644 index eeb0df2747a143..00000000000000 --- a/packages/kbn-elastic-idx/src/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * DeepRequiredArray - * Nested array condition handler - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface DeepRequiredArray extends Array> {} - -/** - * DeepRequiredObject - * Nested object condition handler - */ -type DeepRequiredObject = { [P in keyof T]-?: DeepRequired }; - -/** - * Function that has deeply required return type - */ -type FunctionWithRequiredReturnType any> = T extends ( - ...args: infer A -) => infer R - ? (...args: A) => DeepRequired - : never; - -/** - * DeepRequired - * Required that works for deeply nested structure - */ -type DeepRequired = NonNullable extends never - ? T - : T extends any[] - ? DeepRequiredArray - : T extends (...args: any[]) => any - ? FunctionWithRequiredReturnType - : NonNullable extends object - ? DeepRequiredObject> - : T; - -export function idx( - input: T1, - accessor: (input: NonNullable>) => T2 -): T2 | undefined { - try { - return accessor(input as NonNullable>); - } catch (error) { - return undefined; - } -} diff --git a/packages/kbn-elastic-idx/tsconfig.json b/packages/kbn-elastic-idx/tsconfig.json deleted file mode 100644 index 27833d7594031c..00000000000000 --- a/packages/kbn-elastic-idx/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "./target" - }, - "include": ["src/**/*"] -} diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index 6369a2a3dd5f35..da58881b5b96e8 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -17,7 +17,6 @@ * under the License. */ -import { idx } from '@kbn/elastic-idx'; import { SavedObjectsClientContract, SimpleSavedObject, @@ -64,7 +63,7 @@ export class IndexPatterns { if (!this.savedObjectsCache) { return []; } - return this.savedObjectsCache.map(obj => idx(obj, _ => _.id)); + return this.savedObjectsCache.map(obj => obj?.id); }; getTitles = async (refresh: boolean = false): Promise => { @@ -74,7 +73,7 @@ export class IndexPatterns { if (!this.savedObjectsCache) { return []; } - return this.savedObjectsCache.map(obj => idx(obj, _ => _.attributes.title)); + return this.savedObjectsCache.map(obj => obj?.attributes?.title); }; getFields = async (fields: string[], refresh: boolean = false) => { @@ -86,7 +85,7 @@ export class IndexPatterns { } return this.savedObjectsCache.map((obj: Record) => { const result: Record = {}; - fields.forEach((f: string) => (result[f] = obj[f] || idx(obj, _ => _.attributes[f]))); + fields.forEach((f: string) => (result[f] = obj[f] || obj?.attributes?.[f])); return result; }); }; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts index 9fce720f5b14b6..0c593bec1af3aa 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts @@ -11,7 +11,6 @@ * crashing due to the requirement of the `window` object. */ -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { ReactNode, FunctionComponent } from 'react'; @@ -32,7 +31,7 @@ const layouts: Layouts = { }; export const findLayout = (type: InventoryItemType) => { - const Layout = idx(layouts, _ => _[type]); + const Layout = layouts?.[type]; if (!Layout) { throw new Error( i18n.translate('xpack.infra.inventoryModels.findLayout.error', { diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts index 661b9c7a8841e4..dc3c409ac497e8 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx/target'; import { ReactNode, FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { InventoryItemType } from './types'; @@ -24,7 +23,7 @@ const toolbars: Toolbars = { }; export const findToolbar = (type: InventoryItemType) => { - const Toolbar = idx(toolbars, _ => _[type]); + const Toolbar = toolbars?.[type]; if (!Toolbar) { throw new Error( i18n.translate('xpack.infra.inventoryModels.findToolbar.error', { diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx index a54780267f1c1d..a7dff2f11f2322 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx @@ -8,7 +8,6 @@ import React, { useMemo, useState } from 'react'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { idx } from '@kbn/elastic-idx/target'; import { KFetchError } from 'ui/kfetch/kfetch_error'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { useTrackedPromise } from '../utils/use_tracked_promise'; @@ -44,13 +43,13 @@ export function useHTTPRequest( defaultMessage: `Error`, })} - {idx(err.res, r => r.statusText)} ({idx(err.res, r => r.status)}) + {err.res?.statusText} ({err.res?.status})
{i18n.translate('xpack.infra.useHTTPRequest.error.url', { defaultMessage: `URL`, })}
- {idx(err.res, r => r.url)} + {err.res?.url}
), }); diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts index 01306901e9caa6..834c991d5c6a41 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts @@ -5,7 +5,6 @@ */ import { startsWith, uniq, first } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { RequestHandlerContext } from 'src/core/server'; import { InfraDatabaseSearchResponse } from '../framework'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; @@ -105,8 +104,9 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { const bucketSelector = (response: InfraDatabaseSearchResponse<{}, DataSetResponse>) => (response.aggregations && response.aggregations.datasets.buckets) || []; - const handleAfterKey = createAfterKeyHandler('body.aggs.datasets.composite.after', input => - idx(input, _ => _.aggregations.datasets.after_key) + const handleAfterKey = createAfterKeyHandler( + 'body.aggs.datasets.composite.after', + input => input?.aggregations?.datasets?.after_key ); const buckets = await getAllCompositeData( diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts index 59a4e8911a94d3..95769414832cc0 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { RequestHandlerContext } from 'src/core/server'; import { InfraSnapshotGroupbyInput, @@ -70,8 +69,9 @@ const bucketSelector = ( response: InfraDatabaseSearchResponse<{}, InfraSnapshotAggregationResponse> ) => (response.aggregations && response.aggregations.nodes.buckets) || []; -const handleAfterKey = createAfterKeyHandler('body.aggregations.nodes.composite.after', input => - idx(input, _ => _.aggregations.nodes.after_key) +const handleAfterKey = createAfterKeyHandler( + 'body.aggregations.nodes.composite.after', + input => input?.aggregations?.nodes?.after_key ); const requestGroupedNodes = async ( diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index b9a03fdbc548bd..0642c1fbe61864 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -8,7 +8,6 @@ import { useEffect } from 'react'; import { BehaviorSubject } from 'rxjs'; import { filter, distinctUntilChanged } from 'rxjs/operators'; import { Subscription } from 'rxjs'; -import { idx } from '@kbn/elastic-idx'; import { cloneDeep } from 'lodash'; import { ml } from '../../services/ml_api_service'; import { Dictionary } from '../../../../common/types/common'; @@ -242,12 +241,13 @@ export const useRefreshAnalyticsList = ( const DEFAULT_SIG_FIGS = 3; export function getValuesFromResponse(response: RegressionEvaluateResponse) { - let meanSquaredError = idx(response, _ => _.regression.mean_squared_error.error) as number; + let meanSquaredError = response?.regression?.mean_squared_error?.error; + if (meanSquaredError) { meanSquaredError = Number(meanSquaredError.toPrecision(DEFAULT_SIG_FIGS)); } - let rSquared = idx(response, _ => _.regression.r_squared.value) as number; + let rSquared = response?.regression?.r_squared?.value; if (rSquared) { rSquared = Number(rSquared.toPrecision(DEFAULT_SIG_FIGS)); } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index a650a867aea608..91b73307ef56c3 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -6,7 +6,6 @@ import React, { FC, Fragment, useState, useEffect } from 'react'; import moment from 'moment-timezone'; -import { idx } from '@kbn/elastic-idx'; import { EuiIcon, EuiLoadingSpinner, EuiTabbedContent } from '@elastic/eui'; @@ -64,7 +63,7 @@ export const ExpandedRow: FC = ({ item }) => { const [generalizationEval, setGeneralizationEval] = useState(defaultEval); const [isLoadingTraining, setIsLoadingTraining] = useState(false); const [isLoadingGeneralization, setIsLoadingGeneralization] = useState(false); - const index = idx(item, _ => _.config.dest.index) as string; + const index = item?.config?.dest?.index; const dependentVariable = getDependentVar(item.config.analysis); const predictionFieldName = getPredictionFieldName(item.config.analysis); // default is 'ml' diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 918c42f480e1e5..eda80ca64c86fc 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { validateIndexPattern } from 'ui/index_patterns'; @@ -43,7 +42,7 @@ export const mmlUnitInvalidErrorMessage = i18n.translate( const getSourceIndexString = (state: State) => { const { jobConfig } = state; - const sourceIndex = idx(jobConfig, _ => _.source.index); + const sourceIndex = jobConfig?.source?.index; if (typeof sourceIndex === 'string') { return sourceIndex; @@ -70,17 +69,17 @@ export const validateAdvancedEditor = (state: State): State => { // `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not. // If there are no messages, it means the index pattern is valid. let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0; - const sourceIndex = idx(jobConfig, _ => _.source.index); + const sourceIndex = jobConfig?.source?.index; if (sourceIndexNameValid) { if (typeof sourceIndex === 'string') { sourceIndexNameValid = !sourceIndex.includes(','); } if (Array.isArray(sourceIndex)) { - sourceIndexNameValid = !sourceIndex.some(d => d.includes(',')); + sourceIndexNameValid = !sourceIndex.some(d => d?.includes(',')); } } - const destinationIndexName = idx(jobConfig, _ => _.dest.index) || ''; + const destinationIndexName = jobConfig?.dest?.index ?? ''; const destinationIndexNameEmpty = destinationIndexName === ''; const destinationIndexNameValid = isValidIndexName(destinationIndexName); const destinationIndexPatternTitleExists = diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index ec70c54892a0e0..547a55da7438b5 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - import { getInitialState, getJobConfigFromFormState } from './state'; describe('useCreateAnalyticsForm', () => { @@ -17,15 +15,15 @@ describe('useCreateAnalyticsForm', () => { const jobConfig = getJobConfigFromFormState(state.form); - expect(idx(jobConfig, _ => _.dest.index)).toBe('the-destination-index'); - expect(idx(jobConfig, _ => _.source.index)).toBe('the-source-index'); - expect(idx(jobConfig, _ => _.analyzed_fields.excludes)).toStrictEqual([]); - expect(typeof idx(jobConfig, _ => _.analyzed_fields.includes)).toBe('undefined'); + expect(jobConfig?.dest?.index).toBe('the-destination-index'); + expect(jobConfig?.source?.index).toBe('the-source-index'); + expect(jobConfig?.analyzed_fields?.excludes).toStrictEqual([]); + expect(typeof jobConfig?.analyzed_fields?.includes).toBe('undefined'); // test the conversion of comma-separated Kibana index patterns to ES array based index patterns state.form.sourceIndex = 'the-source-index-1,the-source-index-2'; const jobConfigSourceIndexArray = getJobConfigFromFormState(state.form); - expect(idx(jobConfigSourceIndexArray, _ => _.source.index)).toStrictEqual([ + expect(jobConfigSourceIndexArray?.source?.index).toStrictEqual([ 'the-source-index-1', 'the-source-index-2', ]); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index cac14e654016f4..b2f9442f48edb9 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -7,7 +7,6 @@ import { useReducer } from 'react'; import { i18n } from '@kbn/i18n'; -import { idx } from '@kbn/elastic-idx'; import { SimpleSavedObject } from 'src/core/public'; import { ml } from '../../../../../services/ml_api_service'; @@ -229,9 +228,9 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const indexPatternsMap: SourceIndexMap = {}; const savedObjects = (await kibanaContext.indexPatterns.getCache()) || []; savedObjects.forEach((obj: SimpleSavedObject>) => { - const title = idx(obj, _ => _.attributes.title); + const title = obj?.attributes?.title; if (title !== undefined) { - const id = idx(obj, _ => _.id) || ''; + const id = obj?.id || ''; indexPatternsMap[title] = { label: title, value: id }; } }); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index aaa5142f519ace..e7e5e8aa64f7bf 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Job, Datafeed, Detector } from '../configs'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { @@ -120,10 +119,9 @@ function getDetectors(job: Job, datafeed: Datafeed) { ) { // distinct count detector, field has been removed. // determine field from datafeed aggregations - const field = idx( - datafeed, - _ => _.aggregations.buckets.aggregations.dc_region.cardinality.field - ); + const field = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality + ?.field as string; + if (field !== undefined) { detectors = [ { @@ -193,10 +191,9 @@ function processFieldlessAggs(detectors: Detector[]) { export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean { const detectors = job.analysis_config.detectors; - const distinctCountField = idx( - datafeed, - _ => _.aggregations.buckets.aggregations.dc_region.cardinality.field - ); + const distinctCountField = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality + ?.field as string; + // if distinctCountField is undefined, and any detectors contain a sparse data function // return true if (distinctCountField === undefined) { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts index cfebca70d27b50..0265129d9ccab6 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { mlJobService } from '../../../../services/job_service'; import { loadIndexPatterns, getIndexPatternIdFromName } from '../../../../util/index_utils'; import { CombinedJob } from '../../common/job_creator/configs'; @@ -29,7 +28,7 @@ export async function preConfiguredJobRedirect() { } function getWizardUrlFromCloningJob(job: CombinedJob) { - const created = idx(job, _ => _.custom_settings.created_by); + const created = job?.custom_settings?.created_by; let page = ''; if (created === CREATED_BY_LABEL.SINGLE_METRIC) { diff --git a/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts index 1facc761d6379e..589803b33e11cc 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - // This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. // It splits the attribute key string and uses reduce with an idx check to access nested attributes. export const getNestedProperty = ( @@ -13,5 +11,5 @@ export const getNestedProperty = ( accessor: string, defaultValue?: any ) => { - return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue; + return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue; }; diff --git a/x-pack/legacy/plugins/transform/common/utils/object_utils.ts b/x-pack/legacy/plugins/transform/common/utils/object_utils.ts index 1facc761d6379e..589803b33e11cc 100644 --- a/x-pack/legacy/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/legacy/plugins/transform/common/utils/object_utils.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - // This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. // It splits the attribute key string and uses reduce with an idx check to access nested attributes. export const getNestedProperty = ( @@ -13,5 +11,5 @@ export const getNestedProperty = ( accessor: string, defaultValue?: any ) => { - return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue; + return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue; }; diff --git a/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts b/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts index 955dc831e05e2e..433616e4228021 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - import { TransformId } from './transform'; import { TransformListRow } from './transform_list'; @@ -78,8 +76,7 @@ export function getTransformProgress(item: TransformListRow) { return 100; } - const progress = idx(item, _ => _.stats.checkpointing.next.checkpoint_progress.percent_complete); - + const progress = item?.stats?.checkpointing?.next?.checkpoint_progress?.percent_complete; return progress !== undefined ? Math.round(progress) : undefined; } diff --git a/x-pack/package.json b/x-pack/package.json index 74e6341acc675e..a92839eadefa41 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -183,7 +183,6 @@ "@elastic/numeral": "2.3.3", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", - "@kbn/elastic-idx": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/ui-framework": "1.0.0", From d429a9a1e8b23774968c52deba19bd577f8a112b Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 10 Dec 2019 00:00:36 +0100 Subject: [PATCH 25/36] [ML] Functional tests - fix typing issue (#52167) * Use char by char typing in all text fields * Add dely before first typed charakter when typing char by char * Remove delay before typing again * Use clearCharByChar option for input fields * Revert "Use clearCharByChar option for input fields" This reverts commit e412d7bc641e0eedeeaea28b357709e9be36ac61. * Revert "Use char by char typing in all text fields" This reverts commit 2fbccc57c67040701de69e903d0968726210696f. * Disable jobCreatorUpdate for tests * Revert "Disable jobCreatorUpdate for tests" This reverts commit e178fd82ab1d2b866d1f86fb6e0681ecee994b31. * Check typing char by char for job wizard inputs * Remove .only from anomaly detection suite * Move setValueWithChecks from testSubjects to a ML service --- .../services/machine_learning/common.ts | 73 +++++++++++++++++++ .../services/machine_learning/index.ts | 1 + .../machine_learning/job_wizard_advanced.ts | 15 ++-- .../machine_learning/job_wizard_common.ts | 12 ++- x-pack/test/functional/services/ml.ts | 7 +- 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 x-pack/test/functional/services/machine_learning/common.ts diff --git a/x-pack/test/functional/services/machine_learning/common.ts b/x-pack/test/functional/services/machine_learning/common.ts new file mode 100644 index 00000000000000..12b9e8a1cfb294 --- /dev/null +++ b/x-pack/test/functional/services/machine_learning/common.ts @@ -0,0 +1,73 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +interface SetValueOptions { + clearWithKeyboard?: boolean; + typeCharByChar?: boolean; +} + +export function MachineLearningCommonProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + + return { + async setValueWithChecks( + selector: string, + text: string, + options: SetValueOptions = {} + ): Promise { + return await retry.try(async () => { + const { clearWithKeyboard = false, typeCharByChar = false } = options; + log.debug(`TestSubjects.setValueWithChecks(${selector}, ${text})`); + await testSubjects.click(selector); + // in case the input element is actually a child of the testSubject, we + // call clearValue() and type() on the element that is focused after + // clicking on the testSubject + const input = await find.activeElement(); + + await retry.tryForTime(5000, async () => { + let currentValue = await input.getAttribute('value'); + if (currentValue !== '') { + if (clearWithKeyboard === true) { + await input.clearValueWithKeyboard(); + } else { + await input.clearValue(); + } + currentValue = await input.getAttribute('value'); + } + + if (currentValue === '') { + return true; + } else { + throw new Error(`Expected input to be empty, but got value '${currentValue}'`); + } + }); + + for (const chr of text) { + await retry.tryForTime(5000, async () => { + const oldValue = await input.getAttribute('value'); + await input.type(chr, { charByChar: typeCharByChar }); + + await retry.tryForTime(1000, async () => { + const newValue = await input.getAttribute('value'); + if (newValue === `${oldValue}${chr}`) { + return true; + } else { + throw new Error( + `After typing character '${chr}', the new value in the input should be '${oldValue}${chr}' (got ${newValue})` + ); + } + }); + }); + } + }); + }, + }; +} diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts index adaded08325229..7c393689d166c0 100644 --- a/x-pack/test/functional/services/machine_learning/index.ts +++ b/x-pack/test/functional/services/machine_learning/index.ts @@ -6,6 +6,7 @@ export { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer'; export { MachineLearningAPIProvider } from './api'; +export { MachineLearningCommonProvider } from './common'; export { MachineLearningCustomUrlsProvider } from './custom_urls'; export { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytics'; export { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation'; diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 27768f784b1c53..ab53b0412ca353 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningCommonProvider } from './common'; -export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProviderContext) { +export function MachineLearningJobWizardAdvancedProvider( + { getService }: FtrProviderContext, + mlCommon: ProvidedType +) { const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -44,7 +49,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setQueryDelay(queryDelay: string) { - await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, { + await mlCommon.setValueWithChecks('mlJobWizardInputQueryDelay', queryDelay, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -61,7 +66,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setFrequency(frequency: string) { - await testSubjects.setValue('mlJobWizardInputFrequency', frequency, { + await mlCommon.setValueWithChecks('mlJobWizardInputFrequency', frequency, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -78,7 +83,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setScrollSize(scrollSize: string) { - await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, { + await mlCommon.setValueWithChecks('mlJobWizardInputScrollSize', scrollSize, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -257,7 +262,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setDetectorDescription(description: string) { - await testSubjects.setValue('mlAdvancedDetectorDescriptionInput', description, { + await mlCommon.setValueWithChecks('mlAdvancedDetectorDescriptionInput', description, { clearWithKeyboard: true, }); await this.assertDetectorDescriptionValue(description); diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 235e597f8c2801..b9e6822c8f41a9 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -7,10 +7,12 @@ import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningCommonProvider } from './common'; import { MachineLearningCustomUrlsProvider } from './custom_urls'; export function MachineLearningJobWizardCommonProvider( { getService }: FtrProviderContext, + mlCommon: ProvidedType, customUrls: ProvidedType ) { const comboBox = getService('comboBox'); @@ -113,7 +115,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setBucketSpan(bucketSpan: string) { - await testSubjects.setValue('mlJobWizardInputBucketSpan', bucketSpan, { + await mlCommon.setValueWithChecks('mlJobWizardInputBucketSpan', bucketSpan, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -130,7 +132,9 @@ export function MachineLearningJobWizardCommonProvider( }, async setJobId(jobId: string) { - await testSubjects.setValue('mlJobWizardInputJobId', jobId, { clearWithKeyboard: true }); + await mlCommon.setValueWithChecks('mlJobWizardInputJobId', jobId, { + clearWithKeyboard: true, + }); await this.assertJobIdValue(jobId); }, @@ -146,7 +150,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setJobDescription(jobDescription: string) { - await testSubjects.setValue('mlJobWizardInputJobDescription', jobDescription, { + await mlCommon.setValueWithChecks('mlJobWizardInputJobDescription', jobDescription, { clearWithKeyboard: true, }); await this.assertJobDescriptionValue(jobDescription); @@ -307,7 +311,7 @@ export function MachineLearningJobWizardCommonProvider( await this.ensureAdvancedSectionOpen(); subj = advancedSectionSelector(subj); } - await testSubjects.setValue(subj, modelMemoryLimit, { clearWithKeyboard: true }); + await mlCommon.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); await this.assertModelMemoryLimitValue(modelMemoryLimit, { withAdvancedSection: sectionOptions.withAdvancedSection, }); diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts index 4b6f77262b7f9b..8e71c16921078f 100644 --- a/x-pack/test/functional/services/ml.ts +++ b/x-pack/test/functional/services/ml.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; import { MachineLearningAnomalyExplorerProvider, MachineLearningAPIProvider, + MachineLearningCommonProvider, MachineLearningCustomUrlsProvider, MachineLearningDataFrameAnalyticsProvider, MachineLearningDataFrameAnalyticsCreationProvider, @@ -29,6 +30,8 @@ import { } from './machine_learning'; export function MachineLearningProvider(context: FtrProviderContext) { + const common = MachineLearningCommonProvider(context); + const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context); const api = MachineLearningAPIProvider(context); const customUrls = MachineLearningCustomUrlsProvider(context); @@ -41,8 +44,8 @@ export function MachineLearningProvider(context: FtrProviderContext) { const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); - const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context); - const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, customUrls); + const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, common); + const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, common, customUrls); const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context); const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context); const navigation = MachineLearningNavigationProvider(context); From 21f9ab255a9635e024d300a53a985afa39781476 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Mon, 9 Dec 2019 17:24:58 -0600 Subject: [PATCH 26/36] [Logs UI] Refactor log entry data fetching to hooks (#51526) * Get initialinitial log fetch working with v2 store * Replicate shouldLoadAroundPosition logic within hooks * Reload entries on filter change * Add scroll to load additional entries functionality * Cleanup types types and remove state/remote folder * Typescript cleanup * Remove extraneous console.log * Fix typecheck * Add action to load new entries manually * Typecheck fix * Move v2 store stuff into logs containers * Typecheck fix * More typecheck fix * Remove filterQuery from log highlights redux bridge * Rename LogEntriesDependencies to LogEntriesFetchParams * Fix endless reloading bug * Fix duplicate entry rendering * Make sourceId into a dynamic parameter * Fix bug in pagesAfterEnd not being reported causing endless reload * Fix bugs with live streaming --- .../plugins/infra/public/apps/start_app.tsx | 21 +- .../log_text_stream/loading_item_view.tsx | 2 +- .../scrollable_log_text_stream_view.tsx | 4 +- .../log_text_stream/vertical_scroll_panel.tsx | 6 +- .../logs/log_entries/gql_queries.ts | 64 +++++ .../containers/logs/log_entries/index.ts | 268 ++++++++++++++++++ .../containers/logs/log_entries/types.ts | 75 +++++ .../containers/logs/log_filter/index.ts | 26 ++ .../logs/log_highlights/log_highlights.tsx | 25 +- .../log_highlights/redux_bridge_setters.tsx | 6 - .../logs/log_highlights/redux_bridges.tsx | 13 - .../containers/logs/log_position/index.ts | 35 +++ .../containers/logs/with_stream_items.ts | 122 +++----- .../log_entries.gql_query.ts | 2 +- .../pages/logs/stream/page_logs_content.tsx | 12 +- .../pages/logs/stream/page_providers.tsx | 45 ++- .../plugins/infra/public/store/actions.ts | 1 - .../plugins/infra/public/store/epics.ts | 4 +- .../plugins/infra/public/store/local/epic.ts | 4 +- .../public/store/local/log_position/index.ts | 1 - .../store/local/log_position/reducer.ts | 35 +-- .../store/local/log_position/selectors.ts | 9 +- .../plugins/infra/public/store/reducer.ts | 4 - .../infra/public/store/remote/actions.ts | 7 - .../plugins/infra/public/store/remote/epic.ts | 9 - .../infra/public/store/remote/index.ts | 10 - .../store/remote/log_entries/actions.ts | 21 -- .../public/store/remote/log_entries/epic.ts | 198 ------------- .../public/store/remote/log_entries/index.ts | 13 - .../remote/log_entries/operations/load.ts | 35 --- .../log_entries/operations/load_more.ts | 72 ----- .../store/remote/log_entries/reducer.ts | 17 -- .../store/remote/log_entries/selectors.ts | 71 ----- .../public/store/remote/log_entries/state.ts | 16 -- .../infra/public/store/remote/reducer.ts | 20 -- .../infra/public/store/remote/selectors.ts | 14 - .../plugins/infra/public/store/selectors.ts | 38 --- .../plugins/infra/public/store/store.ts | 6 - .../infra/public/utils/redux_context.tsx | 16 ++ .../remote_state/remote_graphql_state.ts | 214 -------------- 40 files changed, 620 insertions(+), 941 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts rename x-pack/legacy/plugins/infra/public/{store/remote/log_entries/operations => graphql}/log_entries.gql_query.ts (93%) delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/actions.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/epic.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/index.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/reducer.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/remote/selectors.ts create mode 100644 x-pack/legacy/plugins/infra/public/utils/redux_context.tsx delete mode 100644 x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts diff --git a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx index cd28114327b6d2..41479cf6351ecd 100644 --- a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx @@ -21,6 +21,7 @@ import { InfraFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; import { createStore } from '../store'; import { ApolloClientContext } from '../utils/apollo_context'; +import { ReduxStateContextProvider } from '../utils/redux_context'; import { HistoryContext } from '../utils/history_context'; import { useUiSetting$, @@ -46,15 +47,17 @@ export async function startApp(libs: InfraFrontendLibs) { - - - - - - - - - + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx index 549ca4c1ae0478..4cefbea7225ecf 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx @@ -18,7 +18,7 @@ interface LogTextStreamLoadingItemViewProps { hasMore: boolean; isLoading: boolean; isStreaming: boolean; - lastStreamingUpdate: number | null; + lastStreamingUpdate: Date | null; onLoadMore?: () => void; } diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 674c3f59ce957b..a5b85788fdea9c 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -39,7 +39,7 @@ interface ScrollableLogTextStreamViewProps { hasMoreBeforeStart: boolean; hasMoreAfterEnd: boolean; isStreaming: boolean; - lastLoadedTime: number | null; + lastLoadedTime: Date | null; target: TimeKey | null; jumpToTarget: (target: TimeKey) => any; reportVisibleInterval: (params: { @@ -143,7 +143,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< const hasItems = items.length > 0; return ( - {isReloading && !hasItems ? ( + {isReloading && (!isStreaming || !hasItems) ? ( extends React.PureComponent< // Flag the scrollTop change that's about to happen as programmatic, as // opposed to being in direct response to user input this.nextScrollEventFromCenterTarget = true; - scrollRef.current.scrollTop = targetDimensions.top + targetOffset - scrollViewHeight / 2; - return true; + const currentScrollTop = scrollRef.current.scrollTop; + const newScrollTop = targetDimensions.top + targetOffset - scrollViewHeight / 2; + scrollRef.current.scrollTop = newScrollTop; + return currentScrollTop !== newScrollTop; } return false; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts new file mode 100644 index 00000000000000..83bae37c348d4d --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.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 { ApolloClient } from 'apollo-client'; +import { TimeKey } from '../../../../common/time'; +import { logEntriesQuery } from '../../../graphql/log_entries.gql_query'; +import { useApolloClient } from '../../../utils/apollo_context'; +import { LogEntriesResponse } from '.'; + +const LOAD_CHUNK_SIZE = 200; + +type LogEntriesGetter = ( + client: ApolloClient<{}>, + countBefore: number, + countAfter: number +) => (params: { + sourceId: string; + timeKey: TimeKey | null; + filterQuery: string | null; +}) => Promise; + +const getLogEntries: LogEntriesGetter = (client, countBefore, countAfter) => async ({ + sourceId, + timeKey, + filterQuery, +}) => { + if (!timeKey) throw new Error('TimeKey is null'); + const result = await client.query({ + query: logEntriesQuery, + variables: { + sourceId, + timeKey: { time: timeKey.time, tiebreaker: timeKey.tiebreaker }, + countBefore, + countAfter, + filterQuery, + }, + fetchPolicy: 'no-cache', + }); + // Workaround for Typescript. Since we're removing the GraphQL API in another PR or two + // 7.6 goes out I don't think it's worth the effort to actually make this + // typecheck pass + const { source } = result.data as any; + const { logEntriesAround } = source; + return { + entries: logEntriesAround.entries, + entriesStart: logEntriesAround.start, + entriesEnd: logEntriesAround.end, + hasMoreAfterEnd: logEntriesAround.hasMoreAfter, + hasMoreBeforeStart: logEntriesAround.hasMoreBefore, + lastLoadedTime: new Date(), + }; +}; + +export const useGraphQLQueries = () => { + const client = useApolloClient(); + if (!client) throw new Error('Unable to get Apollo Client from context'); + return { + getLogEntriesAround: getLogEntries(client, LOAD_CHUNK_SIZE, LOAD_CHUNK_SIZE), + getLogEntriesBefore: getLogEntries(client, LOAD_CHUNK_SIZE, 0), + getLogEntriesAfter: getLogEntries(client, 0, LOAD_CHUNK_SIZE), + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts new file mode 100644 index 00000000000000..3020ad7eb5f84e --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts @@ -0,0 +1,268 @@ +/* + * 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 { useEffect, useState, useReducer, useCallback } from 'react'; +import createContainer from 'constate'; +import { pick, throttle } from 'lodash'; +import { useGraphQLQueries } from './gql_queries'; +import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; +import { InfraLogEntry } from './types'; + +const DESIRED_BUFFER_PAGES = 2; + +enum Action { + FetchingNewEntries, + FetchingMoreEntries, + ReceiveNewEntries, + ReceiveEntriesBefore, + ReceiveEntriesAfter, + ErrorOnNewEntries, + ErrorOnMoreEntries, +} + +type ReceiveActions = + | Action.ReceiveNewEntries + | Action.ReceiveEntriesBefore + | Action.ReceiveEntriesAfter; + +interface ReceiveEntriesAction { + type: ReceiveActions; + payload: LogEntriesResponse; +} +interface FetchOrErrorAction { + type: Exclude; +} +type ActionObj = ReceiveEntriesAction | FetchOrErrorAction; + +type Dispatch = (action: ActionObj) => void; + +interface LogEntriesProps { + filterQuery: string | null; + timeKey: TimeKey | null; + pagesBeforeStart: number | null; + pagesAfterEnd: number | null; + sourceId: string; + isAutoReloading: boolean; +} + +type FetchEntriesParams = Omit; +type FetchMoreEntriesParams = Pick; + +export interface LogEntriesResponse { + entries: InfraLogEntry[]; + entriesStart: TimeKey | null; + entriesEnd: TimeKey | null; + hasMoreAfterEnd: boolean; + hasMoreBeforeStart: boolean; + lastLoadedTime: Date | null; +} + +export type LogEntriesStateParams = { + isReloading: boolean; + isLoadingMore: boolean; +} & LogEntriesResponse; + +export interface LogEntriesCallbacks { + fetchNewerEntries: () => Promise; +} +export const logEntriesInitialCallbacks = { + fetchNewerEntries: async () => {}, +}; + +export const logEntriesInitialState: LogEntriesStateParams = { + entries: [], + entriesStart: null, + entriesEnd: null, + hasMoreAfterEnd: false, + hasMoreBeforeStart: false, + isReloading: true, + isLoadingMore: false, + lastLoadedTime: null, +}; + +const cleanDuplicateItems = (entriesA: InfraLogEntry[], entriesB: InfraLogEntry[]) => { + const gids = new Set(entriesB.map(item => item.gid)); + return entriesA.filter(item => !gids.has(item.gid)); +}; + +const shouldFetchNewEntries = ({ + prevParams, + timeKey, + filterQuery, + entriesStart, + entriesEnd, +}: FetchEntriesParams & LogEntriesStateParams & { prevParams: FetchEntriesParams }) => { + if (!timeKey) return false; + const shouldLoadWithNewFilter = filterQuery !== prevParams.filterQuery; + const shouldLoadAroundNewPosition = + !entriesStart || !entriesEnd || !timeKeyIsBetween(entriesStart, entriesEnd, timeKey); + return shouldLoadWithNewFilter || shouldLoadAroundNewPosition; +}; + +enum ShouldFetchMoreEntries { + Before, + After, +} + +const shouldFetchMoreEntries = ( + { pagesAfterEnd, pagesBeforeStart }: FetchMoreEntriesParams, + { hasMoreBeforeStart, hasMoreAfterEnd }: LogEntriesStateParams +) => { + if (pagesBeforeStart === null || pagesAfterEnd === null) return false; + if (pagesBeforeStart < DESIRED_BUFFER_PAGES && hasMoreBeforeStart) + return ShouldFetchMoreEntries.Before; + if (pagesAfterEnd < DESIRED_BUFFER_PAGES && hasMoreAfterEnd) return ShouldFetchMoreEntries.After; + return false; +}; + +const useFetchEntriesEffect = ( + state: LogEntriesStateParams, + dispatch: Dispatch, + props: LogEntriesProps +) => { + const { getLogEntriesAround, getLogEntriesBefore, getLogEntriesAfter } = useGraphQLQueries(); + + const [prevParams, cachePrevParams] = useState(props); + const [startedStreaming, setStartedStreaming] = useState(false); + + const runFetchNewEntriesRequest = async () => { + dispatch({ type: Action.FetchingNewEntries }); + try { + const payload = await getLogEntriesAround(props); + dispatch({ type: Action.ReceiveNewEntries, payload }); + } catch (e) { + dispatch({ type: Action.ErrorOnNewEntries }); + } + }; + + const runFetchMoreEntriesRequest = async (direction: ShouldFetchMoreEntries) => { + dispatch({ type: Action.FetchingMoreEntries }); + const getEntriesBefore = direction === ShouldFetchMoreEntries.Before; + const timeKey = getEntriesBefore + ? state.entries[0].key + : state.entries[state.entries.length - 1].key; + const getMoreLogEntries = getEntriesBefore ? getLogEntriesBefore : getLogEntriesAfter; + try { + const payload = await getMoreLogEntries({ ...props, timeKey }); + dispatch({ + type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, + payload, + }); + } catch (e) { + dispatch({ type: Action.ErrorOnMoreEntries }); + } + }; + + const fetchNewEntriesEffectDependencies = Object.values( + pick(props, ['sourceId', 'filterQuery', 'timeKey']) + ); + const fetchNewEntriesEffect = () => { + if (props.isAutoReloading) return; + if (shouldFetchNewEntries({ ...props, ...state, prevParams })) { + runFetchNewEntriesRequest(); + } + cachePrevParams(props); + }; + + const fetchMoreEntriesEffectDependencies = [ + ...Object.values(pick(props, ['pagesAfterEnd', 'pagesBeforeStart'])), + Object.values(pick(state, ['hasMoreBeforeStart', 'hasMoreAfterEnd'])), + ]; + const fetchMoreEntriesEffect = () => { + if (state.isLoadingMore || props.isAutoReloading) return; + const direction = shouldFetchMoreEntries(props, state); + switch (direction) { + case ShouldFetchMoreEntries.Before: + case ShouldFetchMoreEntries.After: + runFetchMoreEntriesRequest(direction); + break; + default: + break; + } + }; + + const fetchNewerEntries = useCallback( + throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), + [props] + ); + + const streamEntriesEffectDependencies = [props.isAutoReloading, state.isLoadingMore]; + const streamEntriesEffect = () => { + (async () => { + if (props.isAutoReloading && !state.isLoadingMore) { + if (startedStreaming) { + await new Promise(res => setTimeout(res, 5000)); + } else { + setStartedStreaming(true); + } + fetchNewerEntries(); + } else if (!props.isAutoReloading) { + setStartedStreaming(false); + } + })(); + }; + + useEffect(fetchNewEntriesEffect, fetchNewEntriesEffectDependencies); + useEffect(fetchMoreEntriesEffect, fetchMoreEntriesEffectDependencies); + useEffect(streamEntriesEffect, streamEntriesEffectDependencies); + + return { fetchNewerEntries }; +}; + +export const useLogEntriesState: ( + props: LogEntriesProps +) => [LogEntriesStateParams, LogEntriesCallbacks] = props => { + const [state, dispatch] = useReducer(logEntriesStateReducer, logEntriesInitialState); + + const { fetchNewerEntries } = useFetchEntriesEffect(state, dispatch, props); + const callbacks = { fetchNewerEntries }; + + return [state, callbacks]; +}; + +const logEntriesStateReducer = (prevState: LogEntriesStateParams, action: ActionObj) => { + switch (action.type) { + case Action.ReceiveNewEntries: + return { ...prevState, ...action.payload, isReloading: false }; + case Action.ReceiveEntriesBefore: { + const prevEntries = cleanDuplicateItems(prevState.entries, action.payload.entries); + const newEntries = [...action.payload.entries, ...prevEntries]; + const { hasMoreBeforeStart, entriesStart, lastLoadedTime } = action.payload; + const update = { + entries: newEntries, + isLoadingMore: false, + hasMoreBeforeStart, + entriesStart, + lastLoadedTime, + }; + return { ...prevState, ...update }; + } + case Action.ReceiveEntriesAfter: { + const prevEntries = cleanDuplicateItems(prevState.entries, action.payload.entries); + const newEntries = [...prevEntries, ...action.payload.entries]; + const { hasMoreAfterEnd, entriesEnd, lastLoadedTime } = action.payload; + const update = { + entries: newEntries, + isLoadingMore: false, + hasMoreAfterEnd, + entriesEnd, + lastLoadedTime, + }; + return { ...prevState, ...update }; + } + case Action.FetchingNewEntries: + return { ...prevState, isReloading: true }; + case Action.FetchingMoreEntries: + return { ...prevState, isLoadingMore: true }; + case Action.ErrorOnNewEntries: + return { ...prevState, isReloading: false }; + case Action.ErrorOnMoreEntries: + return { ...prevState, isLoadingMore: false }; + default: + throw new Error(); + } +}; + +export const LogEntriesState = createContainer(useLogEntriesState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts new file mode 100644 index 00000000000000..75aea8c415eee2 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.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. + */ + +/** A segment of the log entry message that was derived from a field */ +export interface InfraLogMessageFieldSegment { + /** The field the segment was derived from */ + field: string; + /** The segment's message */ + value: string; + /** A list of highlighted substrings of the value */ + highlights: string[]; +} +/** A segment of the log entry message that was derived from a string literal */ +export interface InfraLogMessageConstantSegment { + /** The segment's message */ + constant: string; +} + +export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment; + +/** A special built-in column that contains the log entry's timestamp */ +export interface InfraLogEntryTimestampColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** The timestamp */ + timestamp: number; +} +/** A special built-in column that contains the log entry's constructed message */ +export interface InfraLogEntryMessageColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** A list of the formatted log entry segments */ + message: InfraLogMessageSegment[]; +} + +/** A column that contains the value of a field of the log entry */ +export interface InfraLogEntryFieldColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** The field name of the column */ + field: string; + /** The value of the field in the log entry */ + value: string; + /** A list of highlighted substrings of the value */ + highlights: string[]; +} + +/** A column of a log entry */ +export type InfraLogEntryColumn = + | InfraLogEntryTimestampColumn + | InfraLogEntryMessageColumn + | InfraLogEntryFieldColumn; + +/** A representation of the log entry's position in the event stream */ +export interface InfraTimeKey { + /** The timestamp of the event that the log entry corresponds to */ + time: number; + /** The tiebreaker that disambiguates events with the same timestamp */ + tiebreaker: number; +} + +/** A log entry */ +export interface InfraLogEntry { + /** A unique representation of the log entry's position in the event stream */ + key: InfraTimeKey; + /** The log entry's id */ + gid: string; + /** The source id */ + source: string; + /** The columns used for rendering the log entry */ + columns: InfraLogEntryColumn[]; +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts new file mode 100644 index 00000000000000..a737d19a5923da --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts @@ -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 { useContext } from 'react'; +import createContainer from 'constate'; +import { ReduxStateContext } from '../../../utils/redux_context'; +import { logFilterSelectors as logFilterReduxSelectors } from '../../../store/local/selectors'; + +export const useLogFilterState = () => { + const { local: state } = useContext(ReduxStateContext); + const filterQuery = logFilterReduxSelectors.selectLogFilterQueryAsJson(state); + return { filterQuery }; +}; + +export interface LogFilterStateParams { + filterQuery: string | null; +} + +export const logFilterInitialState = { + filterQuery: null, +}; + +export const LogFilterState = createContainer(useLogFilterState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index 39c088b1ceb5aa..fa1ccb4efa4bb7 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -6,30 +6,30 @@ import createContainer from 'constate'; import { useState, useContext } from 'react'; - import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; import { useReduxBridgeSetters } from './redux_bridge_setters'; import { useLogSummaryBufferInterval } from '../log_summary'; import { LogViewConfiguration } from '../log_view_configuration'; +import { TimeKey } from '../../../../common/time'; export const useLogHighlightsState = ({ sourceId, sourceVersion, + entriesStart, + entriesEnd, + filterQuery, }: { sourceId: string; sourceVersion: string | undefined; + entriesStart: TimeKey | null; + entriesEnd: TimeKey | null; + filterQuery: string | null; }) => { const [highlightTerms, setHighlightTerms] = useState([]); - const { - startKey, - endKey, - filterQuery, visibleMidpoint, - setStartKey, - setEndKey, setFilterQuery, setVisibleMidpoint, jumpToTarget, @@ -50,7 +50,14 @@ export const useLogHighlightsState = ({ logEntryHighlights, logEntryHighlightsById, loadLogEntryHighlightsRequest, - } = useLogEntryHighlights(sourceId, sourceVersion, startKey, endKey, filterQuery, highlightTerms); + } = useLogEntryHighlights( + sourceId, + sourceVersion, + entriesStart, + entriesEnd, + filterQuery, + highlightTerms + ); const { logSummaryHighlights, loadLogSummaryHighlightsRequest } = useLogSummaryHighlights( sourceId, @@ -78,8 +85,6 @@ export const useLogHighlightsState = ({ return { highlightTerms, setHighlightTerms, - setStartKey, - setEndKey, setFilterQuery, logEntryHighlights, logEntryHighlightsById, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx index b3254f597dfcfc..0e778f35188f0a 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx @@ -8,19 +8,13 @@ import { useState } from 'react'; import { TimeKey } from '../../../../common/time'; export const useReduxBridgeSetters = () => { - const [startKey, setStartKey] = useState(null); - const [endKey, setEndKey] = useState(null); const [filterQuery, setFilterQuery] = useState(null); const [visibleMidpoint, setVisibleMidpoint] = useState(null); const [jumpToTarget, setJumpToTarget] = useState<(target: TimeKey) => void>(() => undefined); return { - startKey, - endKey, filterQuery, visibleMidpoint, - setStartKey, - setEndKey, setFilterQuery, setVisibleMidpoint, jumpToTarget, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx index 220eaade12fa60..2b60c6edd97aa0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx @@ -8,23 +8,11 @@ import React, { useEffect, useContext } from 'react'; import { TimeKey } from '../../../../common/time'; import { withLogFilter } from '../with_log_filter'; -import { withStreamItems } from '../with_stream_items'; import { withLogPosition } from '../with_log_position'; import { LogHighlightsState } from './log_highlights'; // Bridges Redux container state with Hooks state. Once state is moved fully from // Redux to Hooks this can be removed. -export const LogHighlightsStreamItemsBridge = withStreamItems( - ({ entriesStart, entriesEnd }: { entriesStart: TimeKey | null; entriesEnd: TimeKey | null }) => { - const { setStartKey, setEndKey } = useContext(LogHighlightsState.Context); - useEffect(() => { - setStartKey(entriesStart); - setEndKey(entriesEnd); - }, [entriesStart, entriesEnd]); - - return null; - } -); export const LogHighlightsPositionBridge = withLogPosition( ({ @@ -61,7 +49,6 @@ export const LogHighlightsFilterQueryBridge = withLogFilter( export const LogHighlightsBridge = ({ indexPattern }: { indexPattern: any }) => ( <> - diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts new file mode 100644 index 00000000000000..7cc8050aafd146 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import createContainer from 'constate'; +import { ReduxStateContext } from '../../../utils/redux_context'; +import { logPositionSelectors as logPositionReduxSelectors } from '../../../store/local/selectors'; +import { TimeKey } from '../../../../common/time'; + +export const useLogPositionState = () => { + const { local: state } = useContext(ReduxStateContext); + const timeKey = logPositionReduxSelectors.selectVisibleMidpointOrTarget(state); + const pages = logPositionReduxSelectors.selectPagesBeforeAndAfter(state); + const isAutoReloading = logPositionReduxSelectors.selectIsAutoReloading(state); + return { timeKey, isAutoReloading, ...pages }; +}; + +export interface LogPositionStateParams { + timeKey: TimeKey | null; + pagesAfterEnd: number | null; + pagesBeforeStart: number | null; + isAutoReloading: boolean; +} + +export const logPositionInitialState = { + timeKey: null, + pagesAfterEnd: null, + pagesBeforeStart: null, + isAutoReloading: false, +}; + +export const LogPositionState = createContainer(useLogPositionState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index 12117d88f8283b..da468b4391e4e1 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -4,85 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useContext, useMemo } from 'react'; -import { connect } from 'react-redux'; - +import { useContext, useMemo } from 'react'; import { StreamItem, LogEntryStreamItem } from '../../components/logging/log_text_stream/item'; -import { logEntriesActions, logEntriesSelectors, logPositionSelectors, State } from '../../store'; import { LogEntry, LogEntryHighlight } from '../../utils/log_entry'; -import { PropsOfContainer, RendererFunction } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; +import { RendererFunction } from '../../utils/typed_react'; // deep inporting to avoid a circular import problem import { LogHighlightsState } from './log_highlights/log_highlights'; +import { LogPositionState } from './log_position'; +import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries'; import { UniqueTimeKey } from '../../../common/time'; -export const withStreamItems = connect( - (state: State) => ({ - isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), - isReloading: logEntriesSelectors.selectIsReloadingEntries(state), - isLoadingMore: logEntriesSelectors.selectIsLoadingMoreEntries(state), - wasAutoReloadJustAborted: logPositionSelectors.selectAutoReloadJustAborted(state), - hasMoreBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart(state), - hasMoreAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd(state), - lastLoadedTime: logEntriesSelectors.selectEntriesLastLoadedTime(state), - entries: logEntriesSelectors.selectEntries(state), - entriesStart: logEntriesSelectors.selectEntriesStart(state), - entriesEnd: logEntriesSelectors.selectEntriesEnd(state), - }), - bindPlainActionCreators({ - loadNewerEntries: logEntriesActions.loadNewerEntries, - reloadEntries: logEntriesActions.reloadEntries, - setSourceId: logEntriesActions.setSourceId, - }) -); - -type WithStreamItemsProps = PropsOfContainer; - -export const WithStreamItems = withStreamItems( - ({ - children, - initializeOnMount, - ...props - }: WithStreamItemsProps & { - children: RendererFunction< - WithStreamItemsProps & { +export const WithStreamItems: React.FunctionComponent<{ + children: RendererFunction< + LogEntriesStateParams & + LogEntriesCallbacks & { currentHighlightKey: UniqueTimeKey | null; items: StreamItem[]; } - >; - initializeOnMount: boolean; - }) => { - const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); - const items = useMemo( - () => - props.isReloading && !props.isAutoReloading && !props.wasAutoReloadJustAborted - ? [] - : props.entries.map(logEntry => - createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) - ), - - [ - props.isReloading, - props.isAutoReloading, - props.wasAutoReloadJustAborted, - props.entries, - logEntryHighlightsById, - ] - ); - - useEffect(() => { - if (initializeOnMount && !props.isReloading && !props.isLoadingMore) { - props.reloadEntries(); - } - }, []); - - return children({ - ...props, - currentHighlightKey, - items, - }); - } -); + >; +}> = ({ children }) => { + const [logEntries, logEntriesCallbacks] = useContext(LogEntriesState.Context); + const { isAutoReloading } = useContext(LogPositionState.Context); + const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); + + const items = useMemo( + () => + logEntries.isReloading && !isAutoReloading + ? [] + : logEntries.entries.map(logEntry => + createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) + ), + + [logEntries.entries, logEntryHighlightsById] + ); + + return children({ + ...logEntries, + ...logEntriesCallbacks, + items, + currentHighlightKey, + }); +}; const createLogEntryStreamItem = ( logEntry: LogEntry, @@ -92,23 +54,3 @@ const createLogEntryStreamItem = ( logEntry, highlights, }); - -/** - * This component serves as connection between the state and side-effects - * managed by redux and the state and effects managed by hooks. In particular, - * it forwards changes of the source id to redux via the action creator - * `setSourceId`. - * - * It will be mounted beneath the hierachy level where the redux store and the - * source state are initialized. Once the log entry state and loading - * side-effects have been migrated from redux to hooks it can be removed. - */ -export const ReduxSourceIdBridge = withStreamItems( - ({ setSourceId, sourceId }: { setSourceId: (sourceId: string) => void; sourceId: string }) => { - useEffect(() => { - setSourceId(sourceId); - }, [setSourceId, sourceId]); - - return null; - } -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts b/x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts similarity index 93% rename from x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts rename to x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts index 78893b83cd645d..41ff3c293a7130 100644 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts +++ b/x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts @@ -6,7 +6,7 @@ import gql from 'graphql-tag'; -import { sharedFragments } from '../../../../../common/graphql/shared'; +import { sharedFragments } from '../../common/graphql/shared'; export const logEntriesQuery = gql` query LogEntries( diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index beb5eb391d3684..88212849d45945 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -24,7 +24,7 @@ import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minima import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position'; import { WithLogPosition } from '../../../containers/logs/with_log_position'; import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview'; -import { ReduxSourceIdBridge, WithStreamItems } from '../../../containers/logs/with_stream_items'; +import { WithStreamItems } from '../../../containers/logs/with_stream_items'; import { Source } from '../../../containers/source'; import { LogsToolbar } from './page_toolbar'; @@ -44,10 +44,8 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { } = useContext(LogFlyoutState.Context); const { logSummaryHighlights } = useContext(LogHighlightsState.Context); const derivedIndexPattern = createDerivedIndexPattern('logs'); - return ( <> - @@ -87,7 +85,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { scrollUnlockLiveStreaming, isScrollLocked, }) => ( - + {({ currentHighlightKey, hasMoreAfterEnd, @@ -96,7 +94,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { isReloading, items, lastLoadedTime, - loadNewerEntries, + fetchNewerEntries, }) => ( { items={items} jumpToTarget={jumpToTargetPosition} lastLoadedTime={lastLoadedTime} - loadNewerItems={loadNewerEntries} + loadNewerItems={fetchNewerEntries} reportVisibleInterval={reportVisiblePositions} scale={textScale} target={targetPosition} @@ -140,7 +138,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { visibleMidpointTime, visibleTimeInterval, }) => ( - + {({ isReloading }) => ( { +const LogEntriesStateProvider: React.FC = ({ children }) => { + const { sourceId } = useContext(Source.Context); + const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext( + LogPositionState.Context + ); + const { filterQuery } = useContext(LogFilterState.Context); + const entriesProps = { + timeKey, + pagesBeforeStart, + pagesAfterEnd, + filterQuery, + sourceId, + isAutoReloading, + }; + return {children}; +}; + +const LogHighlightsStateProvider: React.FC = ({ children }) => { const { sourceId, version } = useContext(Source.Context); + const [{ entriesStart, entriesEnd }] = useContext(LogEntriesState.Context); + const { filterQuery } = useContext(LogFilterState.Context); + const highlightsProps = { + sourceId, + sourceVersion: version, + entriesStart, + entriesEnd, + filterQuery, + }; + return {children}; +}; +export const LogsPageProviders: React.FunctionComponent = ({ children }) => { return ( - - {children} - + + + + {children} + + + ); diff --git a/x-pack/legacy/plugins/infra/public/store/actions.ts b/x-pack/legacy/plugins/infra/public/store/actions.ts index 70855101518d6d..e2be0d64b8f1e3 100644 --- a/x-pack/legacy/plugins/infra/public/store/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/actions.ts @@ -11,4 +11,3 @@ export { waffleTimeActions, waffleOptionsActions, } from './local'; -export { logEntriesActions } from './remote'; diff --git a/x-pack/legacy/plugins/infra/public/store/epics.ts b/x-pack/legacy/plugins/infra/public/store/epics.ts index 4df8e1368ca011..b5e48a4ec6214e 100644 --- a/x-pack/legacy/plugins/infra/public/store/epics.ts +++ b/x-pack/legacy/plugins/infra/public/store/epics.ts @@ -7,7 +7,5 @@ import { combineEpics } from 'redux-observable'; import { createLocalEpic } from './local'; -import { createRemoteEpic } from './remote'; -export const createRootEpic = () => - combineEpics(createLocalEpic(), createRemoteEpic()); +export const createRootEpic = () => combineEpics(createLocalEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/epic.ts index 4cfac85f00b15b..e1a051355576f7 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/epic.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/epic.ts @@ -6,8 +6,6 @@ import { combineEpics } from 'redux-observable'; -import { createLogPositionEpic } from './log_position'; import { createWaffleTimeEpic } from './waffle_time'; -export const createLocalEpic = () => - combineEpics(createLogPositionEpic(), createWaffleTimeEpic()); +export const createLocalEpic = () => combineEpics(createWaffleTimeEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts index 17e06348d18b2a..3edb289985d55c 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts @@ -8,5 +8,4 @@ import * as logPositionActions from './actions'; import * as logPositionSelectors from './selectors'; export { logPositionActions, logPositionSelectors }; -export * from './epic'; export * from './reducer'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts index 3b99e2d4f43794..2ca8be8e40d868 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts @@ -17,8 +17,6 @@ import { unlockAutoReloadScroll, } from './actions'; -import { loadEntriesActionCreators } from '../../remote/log_entries/operations/load'; - interface ManualTargetPositionUpdatePolicy { policy: 'manual'; } @@ -38,9 +36,10 @@ export interface LogPositionState { startKey: TimeKey | null; middleKey: TimeKey | null; endKey: TimeKey | null; + pagesAfterEnd: number; + pagesBeforeStart: number; }; controlsShouldDisplayTargetPosition: boolean; - autoReloadJustAborted: boolean; autoReloadScrollLock: boolean; } @@ -53,9 +52,10 @@ export const initialLogPositionState: LogPositionState = { endKey: null, middleKey: null, startKey: null, + pagesBeforeStart: Infinity, + pagesAfterEnd: Infinity, }, controlsShouldDisplayTargetPosition: false, - autoReloadJustAborted: false, autoReloadScrollLock: false, }; @@ -76,11 +76,16 @@ const targetPositionUpdatePolicyReducer = reducerWithInitialState( const visiblePositionReducer = reducerWithInitialState( initialLogPositionState.visiblePositions -).case(reportVisiblePositions, (state, { startKey, middleKey, endKey }) => ({ - endKey, - middleKey, - startKey, -})); +).case( + reportVisiblePositions, + (state, { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd }) => ({ + endKey, + middleKey, + startKey, + pagesBeforeStart, + pagesAfterEnd, + }) +); // Determines whether to use the target position or the visible midpoint when // displaying a timestamp or time range in the toolbar and log minimap. When the @@ -98,17 +103,6 @@ const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState( return state; }); -// If auto reload is aborted before a pending request finishes, this flag will -// prevent the UI from displaying the Loading Entries screen -const autoReloadJustAbortedReducer = reducerWithInitialState( - initialLogPositionState.autoReloadJustAborted -) - .case(stopAutoReload, () => true) - .case(startAutoReload, () => false) - .case(loadEntriesActionCreators.resolveDone, () => false) - .case(loadEntriesActionCreators.resolveFailed, () => false) - .case(loadEntriesActionCreators.resolve, () => false); - const autoReloadScrollLockReducer = reducerWithInitialState( initialLogPositionState.autoReloadScrollLock ) @@ -122,6 +116,5 @@ export const logPositionReducer = combineReducers({ updatePolicy: targetPositionUpdatePolicyReducer, visiblePositions: visiblePositionReducer, controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, - autoReloadJustAborted: autoReloadJustAbortedReducer, autoReloadScrollLock: autoReloadScrollLockReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts index 7a2fa86822c56b..30fd4d3f77b5cd 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts @@ -15,8 +15,6 @@ export const selectIsAutoReloading = (state: LogPositionState) => export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock; -export const selectAutoReloadJustAborted = (state: LogPositionState) => state.autoReloadJustAborted; - export const selectFirstVisiblePosition = (state: LogPositionState) => state.visiblePositions.startKey ? state.visiblePositions.startKey : null; @@ -26,6 +24,13 @@ export const selectMiddleVisiblePosition = (state: LogPositionState) => export const selectLastVisiblePosition = (state: LogPositionState) => state.visiblePositions.endKey ? state.visiblePositions.endKey : null; +export const selectPagesBeforeAndAfter = (state: LogPositionState) => + state.visiblePositions + ? { + pagesBeforeStart: state.visiblePositions.pagesBeforeStart, + pagesAfterEnd: state.visiblePositions.pagesAfterEnd, + } + : { pagesBeforeStart: null, pagesAfterEnd: null }; export const selectControlsShouldDisplayTargetPosition = (state: LogPositionState) => state.controlsShouldDisplayTargetPosition; diff --git a/x-pack/legacy/plugins/infra/public/store/reducer.ts b/x-pack/legacy/plugins/infra/public/store/reducer.ts index 65b225d0196032..2536ddbee401bf 100644 --- a/x-pack/legacy/plugins/infra/public/store/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/reducer.ts @@ -7,19 +7,15 @@ import { combineReducers } from 'redux'; import { initialLocalState, localReducer, LocalState } from './local'; -import { initialRemoteState, remoteReducer, RemoteState } from './remote'; export interface State { local: LocalState; - remote: RemoteState; } export const initialState: State = { local: initialLocalState, - remote: initialRemoteState, }; export const reducer = combineReducers({ local: localReducer, - remote: remoteReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/actions.ts b/x-pack/legacy/plugins/infra/public/store/remote/actions.ts deleted file mode 100644 index b38890afefb41d..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/actions.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { logEntriesActions } from './log_entries'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/epic.ts b/x-pack/legacy/plugins/infra/public/store/remote/epic.ts deleted file mode 100644 index 3b3ff602731cc1..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/epic.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createLogEntriesEpic } from './log_entries'; - -export const createRemoteEpic = () => createLogEntriesEpic(); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/index.ts b/x-pack/legacy/plugins/infra/public/store/remote/index.ts deleted file mode 100644 index c2843320bfd0cc..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './actions'; -export * from './epic'; -export * from './reducer'; -export * from './selectors'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts deleted file mode 100644 index 02964bcb27e113..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import actionCreatorFactory from 'typescript-fsa'; - -import { loadEntriesActionCreators } from './operations/load'; -import { loadMoreEntriesActionCreators } from './operations/load_more'; - -const actionCreator = actionCreatorFactory('x-pack/infra/remote/log_entries'); - -export const setSourceId = actionCreator('SET_SOURCE_ID'); - -export const loadEntries = loadEntriesActionCreators.resolve; -export const loadMoreEntries = loadMoreEntriesActionCreators.resolve; - -export const loadNewerEntries = actionCreator('LOAD_NEWER_LOG_ENTRIES'); - -export const reloadEntries = actionCreator('RELOAD_LOG_ENTRIES'); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts deleted file mode 100644 index 0894a31996042b..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts +++ /dev/null @@ -1,198 +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 { Action } from 'redux'; -import { combineEpics, Epic, EpicWithState } from 'redux-observable'; -import { merge } from 'rxjs'; -import { exhaustMap, filter, map, withLatestFrom } from 'rxjs/operators'; - -import { logFilterActions, logPositionActions } from '../..'; -import { pickTimeKey, TimeKey, timeKeyIsBetween } from '../../../../common/time'; -import { - loadEntries, - loadMoreEntries, - loadNewerEntries, - reloadEntries, - setSourceId, -} from './actions'; -import { loadEntriesEpic } from './operations/load'; -import { loadMoreEntriesEpic } from './operations/load_more'; - -const LOAD_CHUNK_SIZE = 200; -const DESIRED_BUFFER_PAGES = 2; - -interface ManageEntriesDependencies { - selectLogEntriesStart: (state: State) => TimeKey | null; - selectLogEntriesEnd: (state: State) => TimeKey | null; - selectHasMoreLogEntriesBeforeStart: (state: State) => boolean; - selectHasMoreLogEntriesAfterEnd: (state: State) => boolean; - selectIsAutoReloadingLogEntries: (state: State) => boolean; - selectIsLoadingLogEntries: (state: State) => boolean; - selectLogFilterQueryAsJson: (state: State) => string | null; - selectVisibleLogMidpointOrTarget: (state: State) => TimeKey | null; -} - -export const createLogEntriesEpic = () => - combineEpics( - createEntriesEffectsEpic(), - loadEntriesEpic as EpicWithState, - loadMoreEntriesEpic as EpicWithState - ); - -export const createEntriesEffectsEpic = (): Epic< - Action, - Action, - State, - ManageEntriesDependencies -> => ( - action$, - state$, - { - selectLogEntriesStart, - selectLogEntriesEnd, - selectHasMoreLogEntriesBeforeStart, - selectHasMoreLogEntriesAfterEnd, - selectIsAutoReloadingLogEntries, - selectIsLoadingLogEntries, - selectLogFilterQueryAsJson, - selectVisibleLogMidpointOrTarget, - } -) => { - const filterQuery$ = state$.pipe(map(selectLogFilterQueryAsJson)); - const visibleMidpointOrTarget$ = state$.pipe( - map(selectVisibleLogMidpointOrTarget), - filter(isNotNull), - map(pickTimeKey) - ); - - const sourceId$ = action$.pipe( - filter(setSourceId.match), - map(({ payload }) => payload) - ); - - const shouldLoadAroundNewPosition$ = action$.pipe( - filter(logPositionActions.jumpToTargetPosition.match), - withLatestFrom(state$), - filter(([{ payload }, state]) => { - const entriesStart = selectLogEntriesStart(state); - const entriesEnd = selectLogEntriesEnd(state); - - return entriesStart && entriesEnd - ? !timeKeyIsBetween(entriesStart, entriesEnd, payload) - : true; - }), - map(([{ payload }]) => pickTimeKey(payload)) - ); - - const shouldLoadWithNewFilter$ = action$.pipe( - filter(logFilterActions.applyLogFilterQuery.match), - withLatestFrom(filterQuery$, (filterQuery, filterQueryString) => filterQueryString) - ); - - const shouldReload$ = merge(action$.pipe(filter(reloadEntries.match)), sourceId$); - - const shouldLoadMoreBefore$ = action$.pipe( - filter(logPositionActions.reportVisiblePositions.match), - filter(({ payload: { pagesBeforeStart } }) => pagesBeforeStart < DESIRED_BUFFER_PAGES), - withLatestFrom(state$), - filter( - ([action, state]) => - !selectIsAutoReloadingLogEntries(state) && - !selectIsLoadingLogEntries(state) && - selectHasMoreLogEntriesBeforeStart(state) - ), - map(([action, state]) => selectLogEntriesStart(state)), - filter(isNotNull), - map(pickTimeKey) - ); - - const shouldLoadMoreAfter$ = merge( - action$.pipe( - filter(logPositionActions.reportVisiblePositions.match), - filter(({ payload: { pagesAfterEnd } }) => pagesAfterEnd < DESIRED_BUFFER_PAGES), - withLatestFrom(state$, (action, state) => state), - filter( - state => - !selectIsAutoReloadingLogEntries(state) && - !selectIsLoadingLogEntries(state) && - selectHasMoreLogEntriesAfterEnd(state) - ) - ), - action$.pipe( - filter(loadNewerEntries.match), - withLatestFrom(state$, (action, state) => state) - ) - ).pipe( - map(state => selectLogEntriesEnd(state)), - filter(isNotNull), - map(pickTimeKey) - ); - - return merge( - shouldLoadAroundNewPosition$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadWithNewFilter$.pipe( - withLatestFrom(visibleMidpointOrTarget$, sourceId$), - exhaustMap(([filterQuery, timeKey, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldReload$.pipe( - withLatestFrom(visibleMidpointOrTarget$, filterQuery$, sourceId$), - exhaustMap(([_, timeKey, filterQuery, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadMoreAfter$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadMoreEntries({ - sourceId, - timeKey, - countBefore: 0, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadMoreBefore$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadMoreEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: 0, - filterQuery, - }), - ]) - ) - ); -}; - -const isNotNull = (value: T | null): value is T => value !== null; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts deleted file mode 100644 index 8e004255269355..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts +++ /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 logEntriesActions from './actions'; -import * as logEntriesSelectors from './selectors'; - -export { logEntriesActions, logEntriesSelectors }; -export * from './epic'; -export * from './reducer'; -export { initialLogEntriesState, LogEntriesState } from './state'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts deleted file mode 100644 index ce3193e57ab099..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LogEntries as LogEntriesQuery } from '../../../../graphql/types'; -import { - createGraphqlOperationActionCreators, - createGraphqlOperationReducer, - createGraphqlQueryEpic, -} from '../../../../utils/remote_state/remote_graphql_state'; -import { initialLogEntriesState } from '../state'; -import { logEntriesQuery } from './log_entries.gql_query'; - -const operationKey = 'load'; - -export const loadEntriesActionCreators = createGraphqlOperationActionCreators< - LogEntriesQuery.Query, - LogEntriesQuery.Variables ->('log_entries', operationKey); - -export const loadEntriesReducer = createGraphqlOperationReducer( - operationKey, - initialLogEntriesState, - loadEntriesActionCreators, - (state, action) => action.payload.result.data.source.logEntriesAround, - () => ({ - entries: [], - hasMoreAfter: false, - hasMoreBefore: false, - }) -); - -export const loadEntriesEpic = createGraphqlQueryEpic(logEntriesQuery, loadEntriesActionCreators); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts deleted file mode 100644 index 7651b039083cf3..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LogEntries as LogEntriesQuery } from '../../../../graphql/types'; -import { - getLogEntryIndexAfterTime, - getLogEntryIndexBeforeTime, - getLogEntryKey, -} from '../../../../utils/log_entry'; -import { - createGraphqlOperationActionCreators, - createGraphqlOperationReducer, - createGraphqlQueryEpic, -} from '../../../../utils/remote_state/remote_graphql_state'; -import { initialLogEntriesState } from '../state'; -import { logEntriesQuery } from './log_entries.gql_query'; - -const operationKey = 'load_more'; - -export const loadMoreEntriesActionCreators = createGraphqlOperationActionCreators< - LogEntriesQuery.Query, - LogEntriesQuery.Variables ->('log_entries', operationKey); - -export const loadMoreEntriesReducer = createGraphqlOperationReducer( - operationKey, - initialLogEntriesState, - loadMoreEntriesActionCreators, - (state, action) => { - const logEntriesAround = action.payload.result.data.source.logEntriesAround; - const newEntries = logEntriesAround.entries; - const oldEntries = state && state.entries ? state.entries : []; - const oldStart = state && state.start ? state.start : null; - const oldEnd = state && state.end ? state.end : null; - - if (newEntries.length <= 0) { - return state; - } - - if ((action.payload.params.countBefore || 0) > 0) { - const lastLogEntry = newEntries[newEntries.length - 1]; - const prependAtIndex = getLogEntryIndexAfterTime(oldEntries, getLogEntryKey(lastLogEntry)); - return { - start: logEntriesAround.start, - end: oldEnd, - hasMoreBefore: logEntriesAround.hasMoreBefore, - hasMoreAfter: state ? state.hasMoreAfter : logEntriesAround.hasMoreAfter, - entries: [...newEntries, ...oldEntries.slice(prependAtIndex)], - }; - } else if ((action.payload.params.countAfter || 0) > 0) { - const firstLogEntry = newEntries[0]; - const appendAtIndex = getLogEntryIndexBeforeTime(oldEntries, getLogEntryKey(firstLogEntry)); - return { - start: oldStart, - end: logEntriesAround.end, - hasMoreBefore: state ? state.hasMoreBefore : logEntriesAround.hasMoreBefore, - hasMoreAfter: logEntriesAround.hasMoreAfter, - entries: [...oldEntries.slice(0, appendAtIndex), ...newEntries], - }; - } else { - return state; - } - } -); - -export const loadMoreEntriesEpic = createGraphqlQueryEpic( - logEntriesQuery, - loadMoreEntriesActionCreators -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts deleted file mode 100644 index c0d60c4d336dee..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import reduceReducers from 'reduce-reducers'; -import { Reducer } from 'redux'; - -import { loadEntriesReducer } from './operations/load'; -import { loadMoreEntriesReducer } from './operations/load_more'; -import { LogEntriesState } from './state'; - -export const logEntriesReducer = reduceReducers( - loadEntriesReducer, - loadMoreEntriesReducer -) as Reducer; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts deleted file mode 100644 index 0306efc334a514..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts +++ /dev/null @@ -1,71 +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 { createSelector } from 'reselect'; - -import { createGraphqlStateSelectors } from '../../../utils/remote_state/remote_graphql_state'; -import { LogEntriesRemoteState } from './state'; - -const entriesGraphlStateSelectors = createGraphqlStateSelectors(); - -export const selectEntries = createSelector(entriesGraphlStateSelectors.selectData, data => - data ? data.entries : [] -); - -export const selectIsLoadingEntries = entriesGraphlStateSelectors.selectIsLoading; - -export const selectIsReloadingEntries = createSelector( - entriesGraphlStateSelectors.selectIsLoading, - entriesGraphlStateSelectors.selectLoadingProgressOperationInfo, - (isLoading, operationInfo) => - isLoading && operationInfo ? operationInfo.operationKey === 'load' : false -); - -export const selectIsLoadingMoreEntries = createSelector( - entriesGraphlStateSelectors.selectIsLoading, - entriesGraphlStateSelectors.selectLoadingProgressOperationInfo, - (isLoading, operationInfo) => - isLoading && operationInfo ? operationInfo.operationKey === 'load_more' : false -); - -export const selectEntriesStart = createSelector(entriesGraphlStateSelectors.selectData, data => - data && data.start ? data.start : null -); - -export const selectEntriesEnd = createSelector(entriesGraphlStateSelectors.selectData, data => - data && data.end ? data.end : null -); - -export const selectHasMoreBeforeStart = createSelector( - entriesGraphlStateSelectors.selectData, - data => (data ? data.hasMoreBefore : true) -); - -export const selectHasMoreAfterEnd = createSelector(entriesGraphlStateSelectors.selectData, data => - data ? data.hasMoreAfter : true -); - -export const selectEntriesLastLoadedTime = entriesGraphlStateSelectors.selectLoadingResultTime; - -export const selectEntriesStartLoadingState = entriesGraphlStateSelectors.selectLoadingState; - -export const selectEntriesEndLoadingState = entriesGraphlStateSelectors.selectLoadingState; - -export const selectFirstEntry = createSelector(selectEntries, entries => - entries.length > 0 ? entries[0] : null -); - -export const selectLastEntry = createSelector(selectEntries, entries => - entries.length > 0 ? entries[entries.length - 1] : null -); - -export const selectLoadedEntriesTimeInterval = createSelector( - entriesGraphlStateSelectors.selectData, - data => ({ - end: data && data.end ? data.end.time : null, - start: data && data.start ? data.start.time : null, - }) -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts deleted file mode 100644 index 8dbccf6c2bdd32..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.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 { LogEntries as LogEntriesQuery } from '../../../graphql/types'; -import { - createGraphqlInitialState, - GraphqlState, -} from '../../../utils/remote_state/remote_graphql_state'; - -export type LogEntriesRemoteState = LogEntriesQuery.LogEntriesAround; -export type LogEntriesState = GraphqlState; - -export const initialLogEntriesState = createGraphqlInitialState(); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts b/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts deleted file mode 100644 index 2ab0a9a47ae867..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts +++ /dev/null @@ -1,20 +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 { combineReducers } from 'redux'; -import { initialLogEntriesState, logEntriesReducer, LogEntriesState } from './log_entries'; - -export interface RemoteState { - logEntries: LogEntriesState; -} - -export const initialRemoteState = { - logEntries: initialLogEntriesState, -}; - -export const remoteReducer = combineReducers({ - logEntries: logEntriesReducer, -}); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts b/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts deleted file mode 100644 index b1daa2bc8110df..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts +++ /dev/null @@ -1,14 +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 { globalizeSelectors } from '../../utils/typed_redux'; -import { logEntriesSelectors as innerLogEntriesSelectors } from './log_entries'; -import { RemoteState } from './reducer'; - -export const logEntriesSelectors = globalizeSelectors( - (state: RemoteState) => state.logEntries, - innerLogEntriesSelectors -); diff --git a/x-pack/legacy/plugins/infra/public/store/selectors.ts b/x-pack/legacy/plugins/infra/public/store/selectors.ts index 79a442789d6dde..aecba1779d036e 100644 --- a/x-pack/legacy/plugins/infra/public/store/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/selectors.ts @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createSelector } from 'reselect'; - -import { getLogEntryAtTime } from '../utils/log_entry'; import { globalizeSelectors } from '../utils/typed_redux'; import { logFilterSelectors as localLogFilterSelectors, @@ -16,8 +13,6 @@ import { waffleTimeSelectors as localWaffleTimeSelectors, } from './local'; import { State } from './reducer'; -import { logEntriesSelectors as remoteLogEntriesSelectors } from './remote'; - /** * local selectors */ @@ -29,36 +24,3 @@ export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPosi export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); - -/** - * remote selectors - */ - -const selectRemote = (state: State) => state.remote; - -export const logEntriesSelectors = globalizeSelectors(selectRemote, remoteLogEntriesSelectors); - -/** - * shared selectors - */ - -export const sharedSelectors = { - selectFirstVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectFirstVisiblePosition, - (entries, firstVisiblePosition) => - firstVisiblePosition ? getLogEntryAtTime(entries, firstVisiblePosition) : null - ), - selectMiddleVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectMiddleVisiblePosition, - (entries, middleVisiblePosition) => - middleVisiblePosition ? getLogEntryAtTime(entries, middleVisiblePosition) : null - ), - selectLastVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectLastVisiblePosition, - (entries, lastVisiblePosition) => - lastVisiblePosition ? getLogEntryAtTime(entries, lastVisiblePosition) : null - ), -}; diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index d699db6af042ed..601db0f56a6939 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -12,7 +12,6 @@ import { map } from 'rxjs/operators'; import { createRootEpic, initialState, - logEntriesSelectors, logFilterSelectors, logPositionSelectors, reducer, @@ -38,11 +37,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) const middlewareDependencies = { postToApi$: observableApi.pipe(map(({ post }) => post)), apolloClient$: apolloClient, - selectIsLoadingLogEntries: logEntriesSelectors.selectIsLoadingEntries, - selectLogEntriesEnd: logEntriesSelectors.selectEntriesEnd, - selectLogEntriesStart: logEntriesSelectors.selectEntriesStart, - selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd, - selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart, selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson, diff --git a/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx b/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx new file mode 100644 index 00000000000000..3bd3d31c745a90 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx @@ -0,0 +1,16 @@ +/* + * 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 { connect } from 'react-redux'; +import React, { createContext } from 'react'; +import { State, initialState } from '../store'; + +export const ReduxStateContext = createContext(initialState); + +const withRedux = connect((state: State) => state); +export const ReduxStateContextProvider = withRedux(({ children, ...state }) => { + return {children}; +}); diff --git a/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts b/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts deleted file mode 100644 index 0cbc94516617b2..00000000000000 --- a/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts +++ /dev/null @@ -1,214 +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 { ApolloError, ApolloQueryResult } from 'apollo-client'; -import { DocumentNode } from 'graphql'; -import { Action as ReduxAction } from 'redux'; -import { Epic } from 'redux-observable'; -import { from, Observable } from 'rxjs'; -import { catchError, filter, map, startWith, switchMap, withLatestFrom } from 'rxjs/operators'; -import { Action, ActionCreator, actionCreatorFactory, Failure, Success } from 'typescript-fsa'; -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { createSelector } from 'reselect'; -import { InfraApolloClient } from '../../lib/lib'; -import { - isFailureLoadingResult, - isIdleLoadingProgress, - isRunningLoadingProgress, - isSuccessLoadingResult, - isUninitializedLoadingResult, - LoadingPolicy, - LoadingProgress, - LoadingResult, -} from '../loading_state'; - -export interface GraphqlState { - current: LoadingProgress>; - last: LoadingResult>; - data: State | undefined; -} - -interface OperationInfo { - operationKey: string; - variables: Variables; -} - -type ResolveDonePayload = Success>; -type ResolveFailedPayload = Failure; - -interface OperationActionCreators { - resolve: ActionCreator; - resolveStarted: ActionCreator; - resolveDone: ActionCreator>; - resolveFailed: ActionCreator>; -} - -export const createGraphqlInitialState = (initialData?: State): GraphqlState => ({ - current: { - progress: 'idle', - }, - last: { - result: 'uninitialized', - }, - data: initialData, -}); - -export const createGraphqlOperationActionCreators = ( - stateKey: string, - operationKey: string -): OperationActionCreators => { - const actionCreator = actionCreatorFactory(`x-pack/infra/remote/${stateKey}/${operationKey}`); - - const resolve = actionCreator('RESOLVE'); - const resolveEffect = actionCreator.async>('RESOLVE'); - - return { - resolve, - resolveStarted: resolveEffect.started, - resolveDone: resolveEffect.done, - resolveFailed: resolveEffect.failed, - }; -}; - -export const createGraphqlOperationReducer = ( - operationKey: string, - initialState: GraphqlState, - actionCreators: OperationActionCreators, - reduceSuccess: ( - state: State | undefined, - action: Action> - ) => State | undefined = state => state, - reduceFailure: ( - state: State | undefined, - action: Action> - ) => State | undefined = state => state -) => - reducerWithInitialState(initialState) - .caseWithAction(actionCreators.resolveStarted, (state, action) => ({ - ...state, - current: { - progress: 'running', - time: Date.now(), - parameters: { - operationKey, - variables: action.payload, - }, - }, - })) - .caseWithAction(actionCreators.resolveDone, (state, action) => ({ - ...state, - current: { - progress: 'idle', - }, - last: { - result: 'success', - parameters: { - operationKey, - variables: action.payload.params, - }, - time: Date.now(), - isExhausted: false, - }, - data: reduceSuccess(state.data, action), - })) - .caseWithAction(actionCreators.resolveFailed, (state, action) => ({ - ...state, - current: { - progress: 'idle', - }, - last: { - result: 'failure', - reason: `${action.payload}`, - time: Date.now(), - parameters: { - operationKey, - variables: action.payload.params, - }, - }, - data: reduceFailure(state.data, action), - })) - .build(); - -export const createGraphqlQueryEpic = ( - graphqlQuery: DocumentNode, - actionCreators: OperationActionCreators -): Epic< - ReduxAction, - ReduxAction, - any, - { - apolloClient$: Observable; - } -> => (action$, state$, { apolloClient$ }) => - action$.pipe( - filter(actionCreators.resolve.match), - withLatestFrom(apolloClient$), - switchMap(([{ payload: variables }, apolloClient]) => - from( - apolloClient.query({ - query: graphqlQuery, - variables, - fetchPolicy: 'no-cache', - }) - ).pipe( - map(result => actionCreators.resolveDone({ params: variables, result })), - catchError(error => [actionCreators.resolveFailed({ params: variables, error })]), - startWith(actionCreators.resolveStarted(variables)) - ) - ) - ); - -export const createGraphqlStateSelectors = ( - selectState: (parentState: any) => GraphqlState = parentState => parentState -) => { - const selectData = createSelector(selectState, state => state.data); - - const selectLoadingProgress = createSelector(selectState, state => state.current); - const selectLoadingProgressOperationInfo = createSelector(selectLoadingProgress, progress => - isRunningLoadingProgress(progress) ? progress.parameters : null - ); - const selectIsLoading = createSelector(selectLoadingProgress, isRunningLoadingProgress); - const selectIsIdle = createSelector(selectLoadingProgress, isIdleLoadingProgress); - - const selectLoadingResult = createSelector(selectState, state => state.last); - const selectLoadingResultOperationInfo = createSelector(selectLoadingResult, result => - !isUninitializedLoadingResult(result) ? result.parameters : null - ); - const selectLoadingResultTime = createSelector(selectLoadingResult, result => - !isUninitializedLoadingResult(result) ? result.time : null - ); - const selectIsUninitialized = createSelector(selectLoadingResult, isUninitializedLoadingResult); - const selectIsSuccess = createSelector(selectLoadingResult, isSuccessLoadingResult); - const selectIsFailure = createSelector(selectLoadingResult, isFailureLoadingResult); - - const selectLoadingState = createSelector( - selectLoadingProgress, - selectLoadingResult, - (loadingProgress, loadingResult) => ({ - current: loadingProgress, - last: loadingResult, - policy: { - policy: 'manual', - } as LoadingPolicy, - }) - ); - - return { - selectData, - selectIsFailure, - selectIsIdle, - selectIsLoading, - selectIsSuccess, - selectIsUninitialized, - selectLoadingProgress, - selectLoadingProgressOperationInfo, - selectLoadingResult, - selectLoadingResultOperationInfo, - selectLoadingResultTime, - selectLoadingState, - }; -}; From de4269f8d4d4d8c7685e8de25db1284cd74bb543 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 9 Dec 2019 16:29:47 -0700 Subject: [PATCH 27/36] Fix import causing Kibana to crash in IE11. (#52248) --- .../plugins/canvas/server/lib/build_embeddable_filters.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts index 254a7ad8b36374..52fcc9813a93db 100644 --- a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts +++ b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts @@ -7,7 +7,13 @@ import { Filter } from '../../types'; // @ts-ignore Untyped Local import { buildBoolArray } from './build_bool_array'; -import { TimeRange, esFilters } from '../../../../../../src/plugins/data/server'; + +// TODO: We should be importing from `data/server` below instead of `data/common`, but +// need to keep `data/common` since the contents of this file are currently imported +// by the browser. This file should probably be refactored so that the pieces required +// on the client live in a `public` directory instead. See kibana/issues/52343 +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TimeRange, esFilters } from '../../../../../../src/plugins/data/common'; export interface EmbeddableFilterInput { filters: esFilters.Filter[]; From ca5f6d78f134d79acc24484db90ae1a770a8f146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Mon, 9 Dec 2019 19:30:42 -0500 Subject: [PATCH 28/36] Denormalize actionTypeId into alert actions for easier filtering (#51628) * Denormalize actionTypeId for easier filtering of alerts * Add tests * No longer pass actionTypeId for each alert action in APIs * Add tests to ensure denormalizeActions works on multiple actions * Fix ESLint errors --- x-pack/legacy/plugins/alerting/mappings.json | 3 + .../alerting/server/alerts_client.test.ts | 483 ++++++++++++++++++ .../plugins/alerting/server/alerts_client.ts | 92 ++-- .../lib/create_execution_handler.test.ts | 1 + .../alerting/server/routes/create.test.ts | 36 +- .../plugins/alerting/server/routes/create.ts | 7 +- .../alerting/server/routes/get.test.ts | 1 + .../alerting/server/routes/update.test.ts | 1 + .../plugins/alerting/server/routes/update.ts | 7 +- .../legacy/plugins/alerting/server/types.ts | 2 + .../tests/alerting/alerts.ts | 1 + .../tests/alerting/create.ts | 32 +- .../tests/alerting/find.ts | 38 +- .../spaces_only/tests/alerting/create.ts | 32 +- 14 files changed, 655 insertions(+), 81 deletions(-) diff --git a/x-pack/legacy/plugins/alerting/mappings.json b/x-pack/legacy/plugins/alerting/mappings.json index f840c019d5e024..7a7446602351d5 100644 --- a/x-pack/legacy/plugins/alerting/mappings.json +++ b/x-pack/legacy/plugins/alerting/mappings.json @@ -25,6 +25,9 @@ "actionRef": { "type": "keyword" }, + "actionTypeId": { + "type": "keyword" + }, "params": { "enabled": false, "type": "object" diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 08607f04a52355..8ff54e25a0c992 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -74,6 +74,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -87,6 +99,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -133,6 +146,7 @@ describe('create()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -157,6 +171,7 @@ describe('create()', () => { "actions": Array [ Object { "actionRef": "action_0", + "actionTypeId": "test", "group": "default", "params": Object { "foo": true, @@ -224,6 +239,184 @@ describe('create()', () => { `); }); + test('creates an alert with multiple actions', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + const data = getMockData({ + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + { + id: '2', + type: 'action', + attributes: { + actionTypeId: 'test2', + }, + references: [], + }, + ], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + interval: '10s', + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_1', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_2', + actionTypeId: 'test2', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'action_1', + type: 'action', + id: '1', + }, + { + name: 'action_2', + type: 'action', + id: '2', + }, + ], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + taskType: 'alerting:123', + scheduledAt: new Date(), + attempts: 1, + status: 'idle', + runAt: new Date(), + startedAt: null, + retryAt: null, + state: {}, + params: {}, + ownerId: null, + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + scheduledTaskId: 'task-123', + }, + references: [], + }); + const result = await alertsClient.create({ data }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test2", + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "123", + "id": "1", + "interval": "10s", + "params": Object { + "bar": true, + }, + "scheduledTaskId": "task-123", + } + `); + expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([ + { + id: '1', + type: 'action', + }, + { + id: '2', + type: 'action', + }, + ]); + }); + test('creates a disabled alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData({ enabled: false }); @@ -233,6 +426,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -247,6 +452,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -266,6 +472,7 @@ describe('create()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -306,6 +513,23 @@ describe('create()', () => { ); }); + test('throws error if loading actions fails', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + const data = getMockData(); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.bulkGet.mockRejectedValueOnce(new Error('Test Error')); + await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Test Error"` + ); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + test('throws error if create saved object fails', async () => { const alertsClient = new AlertsClient(alertsClientParams); const data = getMockData(); @@ -315,6 +539,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockRejectedValueOnce(new Error('Test failure')); await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( `"Test failure"` @@ -331,6 +567,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -344,6 +592,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -381,6 +630,18 @@ describe('create()', () => { actionGroups: ['default'], async executor() {}, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -394,6 +655,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -442,6 +704,18 @@ describe('create()', () => { created: true, result: { id: '123', api_key: 'abc' }, }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.create.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -455,6 +729,7 @@ describe('create()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -506,6 +781,7 @@ describe('create()', () => { { actionRef: 'action_0', group: 'default', + actionTypeId: 'test', params: { foo: true }, }, ], @@ -1149,6 +1425,18 @@ describe('update()', () => { references: [], version: '123', }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); savedObjectsClient.update.mockResolvedValueOnce({ id: '1', type: 'alert', @@ -1162,6 +1450,7 @@ describe('update()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -1201,6 +1490,7 @@ describe('update()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -1226,6 +1516,7 @@ describe('update()', () => { "actions": Array [ Object { "actionRef": "action_0", + "actionTypeId": "test", "group": "default", "params": Object { "foo": true, @@ -1262,6 +1553,183 @@ describe('update()', () => { `); }); + it('updates with multiple actions', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + alertTypeId: '123', + scheduledTaskId: 'task-123', + }, + references: [], + version: '123', + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + { + id: '2', + type: 'action', + attributes: { + actionTypeId: 'test2', + }, + references: [], + }, + ], + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + interval: '10s', + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_1', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + { + group: 'default', + actionRef: 'action_2', + actionTypeId: 'test2', + params: { + foo: true, + }, + }, + ], + scheduledTaskId: 'task-123', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + { + name: 'action_1', + type: 'action', + id: '1', + }, + { + name: 'action_2', + type: 'action', + id: '2', + }, + ], + }); + const result = await alertsClient.update({ + id: '1', + data: { + interval: '10s', + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }, + }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test", + "group": "default", + "id": "1", + "params": Object { + "foo": true, + }, + }, + Object { + "actionTypeId": "test2", + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + }, + ], + "enabled": true, + "id": "1", + "interval": "10s", + "params": Object { + "bar": true, + }, + "scheduledTaskId": "task-123", + } + `); + expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([ + { + id: '1', + type: 'action', + }, + { + id: '2', + type: 'action', + }, + ]); + }); + it('calls the createApiKey function', async () => { const alertsClient = new AlertsClient(alertsClientParams); alertTypeRegistry.get.mockReturnValueOnce({ @@ -1281,6 +1749,18 @@ describe('update()', () => { references: [], version: '123', }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ created: true, result: { id: '123', api_key: 'abc' }, @@ -1298,6 +1778,7 @@ describe('update()', () => { { group: 'default', actionRef: 'action_0', + actionTypeId: 'test', params: { foo: true, }, @@ -1338,6 +1819,7 @@ describe('update()', () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "1", "params": Object { @@ -1364,6 +1846,7 @@ describe('update()', () => { "actions": Array [ Object { "actionRef": "action_0", + "actionTypeId": "test", "group": "default", "params": Object { "foo": true, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index 3916ec1d62b6c3..27fda9871e6854 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -21,6 +21,7 @@ interface SuccessCreateAPIKeyResult { result: SecurityPluginCreateAPIKeyResult; } export type CreateAPIKeyResult = FailedCreateAPIKeyResult | SuccessCreateAPIKeyResult; +type NormalizedAlertAction = Omit; interface ConstructorOptions { logger: Logger; @@ -62,9 +63,15 @@ interface CreateOptions { Alert, Exclude< keyof Alert, - 'createdBy' | 'updatedBy' | 'apiKey' | 'apiKeyOwner' | 'muteAll' | 'mutedInstanceIds' + | 'createdBy' + | 'updatedBy' + | 'apiKey' + | 'apiKeyOwner' + | 'muteAll' + | 'mutedInstanceIds' + | 'actions' > - >; + > & { actions: NormalizedAlertAction[] }; options?: { migrationVersion?: Record; }; @@ -76,7 +83,7 @@ interface UpdateOptions { name: string; tags: string[]; interval: string; - actions: AlertAction[]; + actions: NormalizedAlertAction[]; params: Record; }; } @@ -117,8 +124,10 @@ export class AlertsClient { this.validateActions(alertType, data.actions); - const { alert: rawAlert, references } = this.getRawAlert({ + const { references, actions } = await this.denormalizeActions(data.actions); + const rawAlert: RawAlert = { ...data, + actions, createdBy: username, updatedBy: username, apiKeyOwner: apiKey.created && username ? username : undefined, @@ -128,7 +137,7 @@ export class AlertsClient { params: validatedAlertTypeParams, muteAll: false, mutedInstanceIds: [], - }); + }; const createdAlert = await this.savedObjectsClient.create('alert', rawAlert, { ...options, references, @@ -202,7 +211,7 @@ export class AlertsClient { const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); this.validateActions(alertType, data.actions); - const { actions, references } = this.extractReferences(data.actions); + const { actions, references } = await this.denormalizeActions(data.actions); const username = await this.getUserName(); const updatedObject = await this.savedObjectsClient.update( 'alert', @@ -371,26 +380,6 @@ export class AlertsClient { }); } - private extractReferences(actions: Alert['actions']) { - const references: SavedObjectReference[] = []; - const rawActions = actions.map((action, i) => { - const actionRef = `action_${i}`; - references.push({ - name: actionRef, - type: 'action', - id: action.id, - }); - return { - ...omit(action, 'id'), - actionRef, - }; - }) as RawAlert['actions']; - return { - actions: rawActions, - references, - }; - } - private injectReferencesIntoActions( actions: RawAlert['actions'], references: SavedObjectReference[] @@ -426,19 +415,7 @@ export class AlertsClient { }; } - private getRawAlert(alert: Alert): { alert: RawAlert; references: SavedObjectReference[] } { - const { references, actions } = this.extractReferences(alert.actions); - return { - alert: { - ...alert, - actions, - }, - references, - }; - } - - private validateActions(alertType: AlertType, actions: Alert['actions']) { - // TODO: Should also ensure user has access to each action + private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void { const { actionGroups: alertTypeActionGroups } = alertType; const usedAlertActionGroups = actions.map(action => action.group); const invalidActionGroups = usedAlertActionGroups.filter( @@ -455,4 +432,41 @@ export class AlertsClient { ); } } + + private async denormalizeActions( + alertActions: NormalizedAlertAction[] + ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { + // Fetch action objects in bulk + const actionIds = [...new Set(alertActions.map(alertAction => alertAction.id))]; + const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); + const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); + const actionMap = new Map(); + for (const action of bulkGetResult.saved_objects) { + if (action.error) { + throw Boom.badRequest( + `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` + ); + } + actionMap.set(action.id, action); + } + // Extract references and set actionTypeId + const references: SavedObjectReference[] = []; + const actions = alertActions.map(({ id, ...alertAction }, i) => { + const actionRef = `action_${i}`; + references.push({ + id, + name: actionRef, + type: 'action', + }); + return { + ...alertAction, + actionRef, + actionTypeId: actionMap.get(id).attributes.actionTypeId, + }; + }); + return { + actions, + references, + }; + } } diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts b/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts index 4f523f203f87a7..d86a06767c9d16 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts @@ -28,6 +28,7 @@ const createExecutionHandlerParams = { { id: '1', group: 'default', + actionTypeId: 'test', params: { foo: true, contextVal: 'My {{context.value}} goes here', diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index 318dbdf068d6a9..634a797880812c 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -41,6 +41,12 @@ test('creates an alert with proper parameters', async () => { alertsClient.create.mockResolvedValueOnce({ ...mockedAlert, id: '123', + actions: [ + { + ...mockedAlert.actions[0], + actionTypeId: 'test', + }, + ], }); const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); @@ -49,6 +55,7 @@ test('creates an alert with proper parameters', async () => { Object { "actions": Array [ Object { + "actionTypeId": "test", "group": "default", "id": "2", "params": Object { @@ -97,33 +104,4 @@ test('creates an alert with proper parameters', async () => { }, ] `); - expect(alertsClient.create).toHaveBeenCalledTimes(1); - expect(alertsClient.create.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "data": Object { - "actions": Array [ - Object { - "group": "default", - "id": "2", - "params": Object { - "foo": true, - }, - }, - ], - "alertTypeId": "1", - "enabled": true, - "interval": "10s", - "name": "abc", - "params": Object { - "bar": true, - }, - "tags": Array [ - "foo", - ], - "throttle": null, - }, - }, - ] - `); }); diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.ts b/x-pack/legacy/plugins/alerting/server/routes/create.ts index fb82a03f172b3b..cb5277ae191003 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.ts @@ -6,7 +6,6 @@ import Hapi from 'hapi'; import Joi from 'joi'; -import { AlertAction } from '../types'; import { getDurationSchema } from '../lib'; interface ScheduleRequest extends Hapi.Request { @@ -16,7 +15,11 @@ interface ScheduleRequest extends Hapi.Request { tags: string[]; alertTypeId: string; interval: string; - actions: AlertAction[]; + actions: Array<{ + group: string; + id: string; + params: Record; + }>; params: Record; throttle: string | null; }; diff --git a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts index 19618bc9e39feb..4d44ee9dfe6bda 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts @@ -21,6 +21,7 @@ const mockedAlert = { { group: 'default', id: '2', + actionTypeId: 'test', params: { foo: true, }, diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts index 7fc3f459110107..334fb2120319de 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts @@ -24,6 +24,7 @@ const mockedResponse = { { group: 'default', id: '2', + actionTypeId: 'test', params: { baz: true, }, diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.ts b/x-pack/legacy/plugins/alerting/server/routes/update.ts index 6aeedb93a10985..6e8f8557fb24ad 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.ts @@ -6,7 +6,6 @@ import Joi from 'joi'; import Hapi from 'hapi'; -import { AlertAction } from '../types'; import { getDurationSchema } from '../lib'; interface UpdateRequest extends Hapi.Request { @@ -18,7 +17,11 @@ interface UpdateRequest extends Hapi.Request { name: string; tags: string[]; interval: string; - actions: AlertAction[]; + actions: Array<{ + group: string; + id: string; + params: Record; + }>; params: Record; throttle: string | null; }; diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index e2460c549c05db..1bec2632d80822 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -49,12 +49,14 @@ export type AlertActionParams = SavedObjectAttributes; export interface AlertAction { group: string; id: string; + actionTypeId: string; params: AlertActionParams; } export interface RawAlertAction extends SavedObjectAttributes { group: string; actionRef: string; + actionTypeId: string; params: AlertActionParams; } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index edfb340bfd7849..8f3996f958bb23 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -532,6 +532,7 @@ export default function alertTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'superuser at space1': + expect(response.statusCode).to.eql(200); // Wait for actions to execute twice before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.index-record', reference, 2); await alertUtils.disable(response.body.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index bf61ee2e3f1375..c0a99ae068e714 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -31,11 +31,32 @@ export default function createAlertTests({ getService }: FtrProviderContext) { const { user, space } = scenario; describe(scenario.id, () => { it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const response = await supertestWithoutAuth .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData()); + .send( + getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -56,7 +77,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { id: response.body.id, name: 'abc', tags: ['foo'], - actions: [], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.actionTypeId, + group: 'default', + params: {}, + }, + ], enabled: true, alertTypeId: 'test.noop', params: {}, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 31af7a0acffbb8..359058f2ac23af 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -78,10 +78,32 @@ export default function createFindTests({ getService }: FtrProviderContext) { }); it('should handle find alert request with filter appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData()) + .send( + getTestAlertData({ + enabled: false, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ) .expect(200); objectRemover.add(space.id, createdAlert.id, 'alert'); @@ -89,7 +111,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { .get( `${getUrlPrefix( space.id - )}/api/alert/_find?filter=alert.attributes.alertTypeId:test.noop` + )}/api/alert/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` ) .auth(user.username, user.password); @@ -117,11 +139,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { tags: ['foo'], alertTypeId: 'test.noop', interval: '1m', - enabled: true, - actions: [], + enabled: false, + actions: [ + { + id: createdAction.id, + group: 'default', + actionTypeId: 'test.noop', + params: {}, + }, + ], params: {}, createdBy: 'elastic', - scheduledTaskId: match.scheduledTaskId, throttle: '1m', updatedBy: 'elastic', apiKeyOwner: 'elastic', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 3018f8efffffeb..929905a958abbd 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -27,10 +27,31 @@ export default function createAlertTests({ getService }: FtrProviderContext) { } it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const response = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData()); + .send( + getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); expect(response.statusCode).to.eql(200); objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); @@ -38,7 +59,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { id: response.body.id, name: 'abc', tags: ['foo'], - actions: [], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.actionTypeId, + group: 'default', + params: {}, + }, + ], enabled: true, alertTypeId: 'test.noop', params: {}, From 6d5c8caadc8f53de27132032cf6eb12f2df92ee9 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 10 Dec 2019 01:32:20 +0000 Subject: [PATCH 29/36] Adds support for log rotation (#49750) * chore(NA): add log options to config yml * chore(NA): remove unwanted option from config declaration * chore(NA): add the bootstrap for the logging rotate feature * feat(NA): base interface setup for log rotation feature * docs(NA): add documentation for the new logging rotate options. chore(NA): added new schema validations * chore(NA): base lifecycle methods and logic * feat(NA): monitor logic for log rotate feature * fix(NA): basic log rotation lifecycle * chore(NA): fix typo on config file * feat(NA): add rotate files feature to log rotator * chore(NA): fix log rotate config * chore(NA): some tests to try logging rotate lifecycle * feat(NA): correct log rotation logic * fix(NA): lifecycle for the log rotator * test(NA): add a test case * chore(NA): correctly add the new defaults to the config schema * test(NA): change dir generation for test * chore(NA): mock log rotate for logging service test * test(NA): fix temp dir permission issue * test(NA): try to fix test * chore(NA): remove usage of mkdtemp * refact(NA): feature logging rotation reimplementation in order to make it work across platforms * fix(NA): bug on file size monitor handle * fix(NA): remove wrong commented out code * chore(NA): correctly identify if we should use polling * chore(NA): fix some code comment * refact(NA): minor implementation details * chore(NA): change the order of logging mix * test(NA): add some more test cases * test(NA): add the majority of the test cases * test(NA): add last test case * test(NA): fallback conditions * chore(NA): add logging rotate config keys to the docker image * chore(NA): move logging.rotate.enable setting to enabled * chore(NA): clarify documentation for logging rotate * chore(NA): use regular instead of logWithMetadata * chore(NA): move chokidar to a prod dep * chore(NA): add log explaining why we had fallback to use polling * test(NA): fix unit tests * test(NA): fix unit tests * chore(NA): correctly place this.running condition * chore(NA): remove redundant call * fix(NA): log filename containing numbers would produce invalid sorting * chore(NA): remove existsSync function call from readRotatedFilesMetadata function * chore(NA): Update docs/setup/settings.asciidoc Co-Authored-By: Tyler Smalley * chore(NA): Update docs/setup/settings.asciidoc Co-Authored-By: Tyler Smalley * chore(NA): Update docs/setup/settings.asciidoc Co-Authored-By: Tyler Smalley * chore(NA): Update docs/setup/settings.asciidoc Co-Authored-By: Tyler Smalley * chore(na): update src/legacy/server/logging/rotate/index.js Co-Authored-By: Tyler Smalley * chore(NA): remove unused config line from docker vars * chore(NA): update documentation to include info about non exact limits * chore(NA): remove redudant if clause * fix(NA): correctly work with new keepFiles limit after start * fix(NA): warning log for logging rotate * chore(NA): replace logwithmetadate with log * docs(NA): correct log to right terms * docs(NA): add comment about usage of slice(-1) * refact(NA): changing polling interval from seconds to milliseconds * docs(NA): fix comments for shouldRotate method * chore(NA): update src/legacy/server/logging/rotate/log_rotator.js Co-Authored-By: Mikhail Shustov * chore(NA): update src/legacy/server/logging/rotate/log_rotator.js Co-Authored-By: Mikhail Shustov * refact(NA): small change * refact(NA): bound stop * refact(NA): shouldUsePolling test function * refact(NA): move named truncate function to delete * refact(NA): typescript conversion * chore(NA): type update for log rotation index file * docs(NA): add experimental tag on docs * chore(NA): add call protection of clearTimeout * refact(NA): cleanup comments and wrong added logs plus inline config * chore(NA): replace ts-ignore by non null assertion operator * docs(NA): extend documentation for _renameRotatedFilesByOne call * chore(NA): fix type problems for process.emit on nodejs --- docs/setup/settings.asciidoc | 30 ++ package.json | 2 +- src/cli/cluster/cluster_manager.js | 12 + src/cli/cluster/worker.js | 3 + src/core/server/bootstrap.ts | 17 +- .../server/logging/logging_service.test.ts | 4 + .../resources/bin/kibana-docker | 5 + src/legacy/server/config/schema.js | 9 +- src/legacy/server/logging/index.js | 5 +- src/legacy/server/logging/rotate/index.ts | 59 +++ .../server/logging/rotate/log_rotator.test.ts | 265 +++++++++++++ .../server/logging/rotate/log_rotator.ts | 359 ++++++++++++++++++ 12 files changed, 765 insertions(+), 5 deletions(-) create mode 100644 src/legacy/server/logging/rotate/index.ts create mode 100644 src/legacy/server/logging/rotate/log_rotator.test.ts create mode 100644 src/legacy/server/logging/rotate/log_rotator.ts diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 5605ed5c56688c..39c87d97af4ba9 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -147,6 +147,36 @@ will default to `true`. `logging.quiet:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output other than error messages. +`logging.rotate:`:: [experimental] Specifies the options for the logging rotate feature. +When not defined, all the sub options defaults would be applied. +The following example shows a valid logging rotate configuration: ++ +-- + logging.rotate: + enabled: true + everyBytes: 10485760 + keepFiles: 10 +-- + +`logging.rotate.enabled:`:: [experimental] *Default: false* Set the value of this setting to `true` to +enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` +that feature would not take any effect. + +`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the +limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and +this option should be at least greater than 1024. + +`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep +on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` +option has to be in the range of 2 to 1024 files. + +`logging.rotate.pollingInterval:`:: [experimental] *Default: 10000* The number of milliseconds for the polling strategy in case +the `logging.rotate.usePolling` is enabled. That option has to be in the range of 5000 to 3600000 milliseconds. + +`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring +the log file. However, there is some systems where it could not be always accurate. In those cases, if needed, +the `polling` method could be used enabling that option. + `logging.silent:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output. diff --git a/package.json b/package.json index edd2b621296276..8ccb138a3aced5 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "cache-loader": "^4.1.0", "chalk": "^2.4.2", "check-disk-space": "^2.1.0", + "chokidar": "3.2.1", "color": "1.0.3", "commander": "3.0.0", "compare-versions": "3.5.1", @@ -373,7 +374,6 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chokidar": "3.2.1", "chromedriver": "78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js index 8ddeda93e6a7e1..050d13b4b2c3e8 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.js @@ -90,6 +90,18 @@ export default class ClusterManager { }); }); + // When receive that event from server worker + // forward a reloadLoggingConfig message to master + // and all workers. This is only used by LogRotator service + // when the cluster mode is enabled + this.server.on('reloadLoggingConfigFromServerWorker', () => { + process.emit('message', { reloadLoggingConfig: true }); + + this.workers.forEach(worker => { + worker.fork.send({ reloadLoggingConfig: true }); + }); + }); + bindAll(this, 'onWatcherAdd', 'onWatcherError', 'onWatcherChange'); if (opts.open) { diff --git a/src/cli/cluster/worker.js b/src/cli/cluster/worker.js index 4d9aba93d61dba..254cce483cb7c6 100644 --- a/src/cli/cluster/worker.js +++ b/src/cli/cluster/worker.js @@ -134,6 +134,9 @@ export default class Worker extends EventEmitter { this.listening = true; this.emit('listening'); break; + case 'RELOAD_LOGGING_CONFIG_FROM_SERVER_WORKER': + this.emit('reloadLoggingConfigFromServerWorker'); + break; } } diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts index a78de8dfe17e45..a0cf3d1602879e 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/server/bootstrap.ts @@ -71,7 +71,20 @@ export async function bootstrap({ const root = new Root(rawConfigService.getConfig$(), env, onRootShutdown); - process.on('SIGHUP', () => { + process.on('SIGHUP', () => reloadLoggingConfig()); + + // This is only used by the LogRotator service + // in order to be able to reload the log configuration + // under the cluster mode + process.on('message', msg => { + if (!msg || msg.reloadLoggingConfig !== true) { + return; + } + + reloadLoggingConfig(); + }); + + function reloadLoggingConfig() { const cliLogger = root.logger.get('cli'); cliLogger.info('Reloading logging configuration due to SIGHUP.', { tags: ['config'] }); @@ -82,7 +95,7 @@ export async function bootstrap({ } cliLogger.info('Reloaded logging configuration due to SIGHUP.', { tags: ['config'] }); - }); + } process.on('SIGINT', () => shutdown()); process.on('SIGTERM', () => shutdown()); diff --git a/src/core/server/logging/logging_service.test.ts b/src/core/server/logging/logging_service.test.ts index 380488ff9f62df..c58103cca5f8d6 100644 --- a/src/core/server/logging/logging_service.test.ts +++ b/src/core/server/logging/logging_service.test.ts @@ -23,6 +23,10 @@ jest.mock('fs', () => ({ createWriteStream: jest.fn(() => ({ write: mockStreamWrite })), })); +jest.mock('../../../legacy/server/logging/rotate', () => ({ + setupLoggingRotate: jest.fn().mockImplementation(() => Promise.resolve({})), +})); + const timestamp = new Date(Date.UTC(2012, 1, 1)); let mockConsoleLog: jest.SpyInstance; diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 0c8faf47411d4e..d1734e836d9831 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -42,6 +42,11 @@ kibana_vars=( kibana.index logging.dest logging.quiet + logging.rotate.enabled + logging.rotate.everyBytes + logging.rotate.keepFiles + logging.rotate.pollingInterval + logging.rotate.usePolling logging.silent logging.useUTC logging.verbose diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 24fe4e1a4d9866..a19a39da0f6dd9 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -135,7 +135,14 @@ export default () => Joi.object({ then: Joi.default(!process.stdout.isTTY), otherwise: Joi.default(true) }), - timezone: Joi.string() + timezone: Joi.string(), + rotate: Joi.object().keys({ + enabled: Joi.boolean().default(false), + everyBytes: Joi.number().greater(1024).default(10485760), + keepFiles: Joi.number().greater(2).less(1024).default(7), + pollingInterval: Joi.number().greater(5000).less(3600000).default(10000), + usePolling: Joi.boolean().default(false) + }).default() }).default(), ops: Joi.object({ diff --git a/src/legacy/server/logging/index.js b/src/legacy/server/logging/index.js index 6e07757abf7bc4..defbcd3331df49 100644 --- a/src/legacy/server/logging/index.js +++ b/src/legacy/server/logging/index.js @@ -20,6 +20,7 @@ import good from '@elastic/good'; import loggingConfiguration from './configuration'; import { logWithMetadata } from './log_with_metadata'; +import { setupLoggingRotate } from './rotate'; export async function setupLogging(server, config) { return await server.register({ @@ -30,5 +31,7 @@ export async function setupLogging(server, config) { export async function loggingMixin(kbnServer, server, config) { logWithMetadata.decorateServer(server); - return await setupLogging(server, config); + + await setupLogging(server, config); + await setupLoggingRotate(server, config); } diff --git a/src/legacy/server/logging/rotate/index.ts b/src/legacy/server/logging/rotate/index.ts new file mode 100644 index 00000000000000..646c89efe8e20e --- /dev/null +++ b/src/legacy/server/logging/rotate/index.ts @@ -0,0 +1,59 @@ +/* + * 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 { isMaster, isWorker } from 'cluster'; +import { Server } from 'hapi'; +import { LogRotator } from './log_rotator'; +import { KibanaConfig } from '../../kbn_server'; + +let logRotator: LogRotator; + +export async function setupLoggingRotate(server: Server, config: KibanaConfig) { + // If log rotate is not enabled we skip + if (!config.get('logging.rotate.enabled')) { + return; + } + + // We just want to start the logging rotate service once + // and we choose to use the master (prod) or the worker server (dev) + if (!isMaster && isWorker && process.env.kbnWorkerType !== 'server') { + return; + } + + // We don't want to run logging rotate server if + // we are not logging to a file + if (config.get('logging.dest') === 'stdout') { + server.log( + ['warning', 'logging:rotate'], + 'Log rotation is enabled but logging.dest is configured for stdout. Set logging.dest to a file for this setting to take effect.' + ); + return; + } + + // Enable Logging Rotate Service + // We need the master process and it can + // try to setupLoggingRotate more than once, + // so we'll need to assure it only loads once. + if (!logRotator) { + logRotator = new LogRotator(config, server); + await logRotator.start(); + } + + return logRotator; +} diff --git a/src/legacy/server/logging/rotate/log_rotator.test.ts b/src/legacy/server/logging/rotate/log_rotator.test.ts new file mode 100644 index 00000000000000..c2100546364d41 --- /dev/null +++ b/src/legacy/server/logging/rotate/log_rotator.test.ts @@ -0,0 +1,265 @@ +/* + * 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 del from 'del'; +import fs, { existsSync, mkdirSync, statSync, writeFileSync } from 'fs'; +import { LogRotator } from './log_rotator'; +import { tmpdir } from 'os'; +import { dirname, join } from 'path'; + +const mockOn = jest.fn(); +jest.mock('chokidar', () => ({ + watch: jest.fn(() => ({ + on: mockOn, + close: jest.fn(), + })), +})); + +jest.mock('lodash', () => ({ + ...require.requireActual('lodash'), + throttle: (fn: any) => fn, +})); + +const tempDir = join(tmpdir(), 'kbn_log_rotator_test'); +const testFilePath = join(tempDir, 'log_rotator_test_log_file.log'); + +const createLogRotatorConfig: any = (logFilePath: string) => { + return new Map([ + ['logging.dest', logFilePath], + ['logging.rotate.everyBytes', 2], + ['logging.rotate.keepFiles', 2], + ['logging.rotate.usePolling', false], + ['logging.rotate.pollingInterval', 10000], + ] as any); +}; + +const mockServer: any = { + log: jest.fn(), +}; + +const writeBytesToFile = (filePath: string, numberOfBytes: number) => { + writeFileSync(filePath, 'a'.repeat(numberOfBytes), { flag: 'a' }); +}; + +describe('LogRotator', () => { + beforeEach(() => { + mkdirSync(tempDir, { recursive: true }); + writeFileSync(testFilePath, ''); + }); + + afterEach(() => { + del.sync(dirname(testFilePath), { force: true }); + mockOn.mockClear(); + }); + + it('rotates log file when bigger than set limit on start', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + await logRotator.stop(); + const testLogFileDir = dirname(testFilePath); + + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file when equal than set limit over time', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeFalsy(); + + writeBytesToFile(testFilePath, 1); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file when file size is bigger than limit', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeFalsy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 3 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + }); + + it('rotates log file service correctly keeps number of files', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.2'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + }); + + it('rotates log file service correctly keeps number of files even when number setting changes', async () => { + writeBytesToFile(testFilePath, 3); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + + const testLogFileDir = dirname(testFilePath); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + + writeBytesToFile(testFilePath, 2); + + // ['change', [asyncFunction]] + const onChangeCb = mockOn.mock.calls[0][1]; + await onChangeCb(testLogFileDir, { size: 2 }); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.2'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + + logRotator.keepFiles = 1; + await logRotator.start(); + + writeBytesToFile(testFilePath, 5); + await onChangeCb(testLogFileDir, { size: 5 }); + + await logRotator.stop(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0'))).toBeTruthy(); + expect(existsSync(join(testLogFileDir, 'log_rotator_test_log_file.log.1'))).toBeFalsy(); + expect(statSync(join(testLogFileDir, 'log_rotator_test_log_file.log.0')).size).toBe(5); + }); + + it('rotates log file service correctly detects usePolling when it should be false', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(false); + + const usePolling = await logRotator._shouldUsePolling(); + expect(usePolling).toBe(false); + + await logRotator.stop(); + }); + + it('rotates log file service correctly detects usePolling when it should be true', async () => { + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + + jest.spyOn(fs, 'watch').mockImplementation( + () => + ({ + on: jest.fn((eventType, cb) => { + if (eventType === 'error') { + cb(); + } + }), + close: jest.fn(), + } as any) + ); + + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(true); + + await logRotator.stop(); + }); + + it('rotates log file service correctly fallback to usePolling true after defined timeout', async () => { + jest.useFakeTimers(); + writeBytesToFile(testFilePath, 1); + + const logRotator = new LogRotator(createLogRotatorConfig(testFilePath), mockServer); + jest.spyOn(logRotator, '_sendReloadLogConfigSignal').mockImplementation(() => {}); + jest.spyOn(fs, 'watch').mockImplementation( + () => + ({ + on: jest.fn((ev: string) => { + if (ev === 'error') { + jest.runTimersToTime(15000); + } + }), + close: jest.fn(), + } as any) + ); + + await logRotator.start(); + + expect(logRotator.running).toBe(true); + expect(logRotator.usePolling).toBe(true); + + await logRotator.stop(); + jest.useRealTimers(); + }); +}); diff --git a/src/legacy/server/logging/rotate/log_rotator.ts b/src/legacy/server/logging/rotate/log_rotator.ts new file mode 100644 index 00000000000000..3662910ca5a7ba --- /dev/null +++ b/src/legacy/server/logging/rotate/log_rotator.ts @@ -0,0 +1,359 @@ +/* + * 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 * as chokidar from 'chokidar'; +import { isMaster } from 'cluster'; +import fs from 'fs'; +import { Server } from 'hapi'; +import { throttle } from 'lodash'; +import { tmpdir } from 'os'; +import { basename, dirname, join, sep } from 'path'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { promisify } from 'util'; +import { KibanaConfig } from '../../kbn_server'; + +const mkdirAsync = promisify(fs.mkdir); +const readdirAsync = promisify(fs.readdir); +const renameAsync = promisify(fs.rename); +const statAsync = promisify(fs.stat); +const unlinkAsync = promisify(fs.unlink); +const writeFileAsync = promisify(fs.writeFile); + +export class LogRotator { + private readonly config: KibanaConfig; + private readonly log: Server['log']; + public logFilePath: string; + public everyBytes: number; + public keepFiles: number; + public running: boolean; + private logFileSize: number; + public isRotating: boolean; + public throttledRotate: () => void; + public stalker: chokidar.FSWatcher | null; + public usePolling: boolean; + public pollingInterval: number; + private stalkerUsePollingPolicyTestTimeout: NodeJS.Timeout | null; + + constructor(config: KibanaConfig, server: Server) { + this.config = config; + this.log = server.log.bind(server); + this.logFilePath = config.get('logging.dest'); + this.everyBytes = config.get('logging.rotate.everyBytes'); + this.keepFiles = config.get('logging.rotate.keepFiles'); + this.running = false; + this.logFileSize = 0; + this.isRotating = false; + this.throttledRotate = throttle(async () => await this._rotate(), 5000); + this.stalker = null; + this.usePolling = config.get('logging.rotate.usePolling'); + this.pollingInterval = config.get('logging.rotate.pollingInterval'); + this.stalkerUsePollingPolicyTestTimeout = null; + } + + async start() { + if (this.running) { + return; + } + + this.running = true; + + // create exit listener for cleanup purposes + this._createExitListener(); + + // call rotate on startup + await this._callRotateOnStartup(); + + // init log file size monitor + await this._startLogFileSizeMonitor(); + } + + stop = () => { + if (!this.running) { + return; + } + + // cleanup exit listener + this._deleteExitListener(); + + // stop log file size monitor + this._stopLogFileSizeMonitor(); + + this.running = false; + }; + + async _shouldUsePolling() { + try { + // Setup a test file in order to try the fs env + // and understand if we need to usePolling or not + const tempFileDir = tmpdir(); + const tempFile = join(tempFileDir, 'kbn_log_rotation_use_polling_test_file.log'); + + await mkdirAsync(tempFileDir, { recursive: true }); + await writeFileAsync(tempFile, ''); + + // setup fs.watch for the temp test file + const testWatcher = fs.watch(tempFile, { persistent: false }); + + // await writeFileAsync(tempFile, 'test'); + + const usePollingTest$ = new Observable(observer => { + // observable complete function + const completeFn = (completeStatus: boolean) => { + if (this.stalkerUsePollingPolicyTestTimeout) { + clearTimeout(this.stalkerUsePollingPolicyTestTimeout); + } + testWatcher.close(); + + observer.next(completeStatus); + observer.complete(); + }; + + // setup conditions that would fire the observable + this.stalkerUsePollingPolicyTestTimeout = setTimeout(() => completeFn(true), 15000); + testWatcher.on('change', () => completeFn(false)); + testWatcher.on('error', () => completeFn(true)); + + // fire test watcher events + setTimeout(() => { + fs.writeFileSync(tempFile, 'test'); + }, 0); + }); + + // wait for the first observable result and consider it as the result + // for our use polling test + const usePollingTestResult = await usePollingTest$.pipe(first()).toPromise(); + + // delete the temp file used for the test + await unlinkAsync(tempFile); + + return usePollingTestResult; + } catch { + return true; + } + } + + async _startLogFileSizeMonitor() { + this.usePolling = await this._shouldUsePolling(); + + if (this.usePolling && this.usePolling !== this.config.get('logging.rotate.usePolling')) { + this.log( + ['warning', 'logging:rotate'], + 'The current environment does not support `fs.watch`. Falling back to polling using `fs.watchFile`' + ); + } + + this.stalker = chokidar.watch(this.logFilePath, { + ignoreInitial: true, + awaitWriteFinish: false, + useFsEvents: false, + usePolling: this.usePolling, + interval: this.pollingInterval, + binaryInterval: this.pollingInterval, + alwaysStat: true, + atomic: false, + }); + this.stalker.on('change', this._logFileSizeMonitorHandler); + } + + _logFileSizeMonitorHandler = async (filename: string, stats: fs.Stats) => { + if (!filename || !stats) { + return; + } + + this.logFileSize = stats.size || 0; + await this.throttledRotate(); + }; + + _stopLogFileSizeMonitor() { + if (!this.stalker) { + return; + } + + this.stalker.close(); + + if (this.stalkerUsePollingPolicyTestTimeout) { + clearTimeout(this.stalkerUsePollingPolicyTestTimeout); + } + } + + _createExitListener() { + process.on('exit', this.stop); + } + + _deleteExitListener() { + process.removeListener('exit', this.stop); + } + + async _getLogFileSizeAndCreateIfNeeded() { + try { + const logFileStats = await statAsync(this.logFilePath); + return logFileStats.size; + } catch { + // touch the file to make the watcher being able to register + // change events + await writeFileAsync(this.logFilePath, ''); + return 0; + } + } + + async _callRotateOnStartup() { + this.logFileSize = await this._getLogFileSizeAndCreateIfNeeded(); + await this._rotate(); + } + + _shouldRotate() { + // should rotate evaluation + // 1. should rotate if current log size exceeds + // the defined one on everyBytes + // 2. should not rotate if is already rotating or if any + // of the conditions on 1. do not apply + if (this.isRotating) { + return false; + } + + return this.logFileSize >= this.everyBytes; + } + + async _rotate() { + if (!this._shouldRotate()) { + return; + } + + await this._rotateNow(); + } + + async _rotateNow() { + // rotate process + // 1. get rotated files metadata (list of log rotated files present on the log folder, numerical sorted) + // 2. delete last file + // 3. rename all files to the correct index +1 + // 4. rename + compress current log into 1 + // 5. send SIGHUP to reload log config + + // rotate process is starting + this.isRotating = true; + + // get rotated files metadata + const foundRotatedFiles = await this._readRotatedFilesMetadata(); + + // delete number of rotated files exceeding the keepFiles limit setting + const rotatedFiles: string[] = await this._deleteFoundRotatedFilesAboveKeepFilesLimit( + foundRotatedFiles + ); + + // delete last file + await this._deleteLastRotatedFile(rotatedFiles); + + // rename all files to correct index + 1 + // and normalize numbering if by some reason + // (for example log file deletion) that numbering + // was interrupted + await this._renameRotatedFilesByOne(rotatedFiles); + + // rename current log into 0 + await this._rotateCurrentLogFile(); + + // send SIGHUP to reload log configuration + this._sendReloadLogConfigSignal(); + + // Reset log file size + this.logFileSize = 0; + + // rotate process is finished + this.isRotating = false; + } + + async _readRotatedFilesMetadata() { + const logFileBaseName = basename(this.logFilePath); + const logFilesFolder = dirname(this.logFilePath); + const foundLogFiles: string[] = await readdirAsync(logFilesFolder); + + return ( + foundLogFiles + .filter(file => new RegExp(`${logFileBaseName}\\.\\d`).test(file)) + // we use .slice(-1) here in order to retrieve the last number match in the read filenames + .sort((a, b) => Number(a.match(/(\d+)/g)!.slice(-1)) - Number(b.match(/(\d+)/g)!.slice(-1))) + .map(filename => `${logFilesFolder}${sep}${filename}`) + ); + } + + async _deleteFoundRotatedFilesAboveKeepFilesLimit(foundRotatedFiles: string[]) { + if (foundRotatedFiles.length <= this.keepFiles) { + return foundRotatedFiles; + } + + const finalRotatedFiles = foundRotatedFiles.slice(0, this.keepFiles); + const rotatedFilesToDelete = foundRotatedFiles.slice( + finalRotatedFiles.length, + foundRotatedFiles.length + ); + + await Promise.all( + rotatedFilesToDelete.map((rotatedFilePath: string) => unlinkAsync(rotatedFilePath)) + ); + + return finalRotatedFiles; + } + + async _deleteLastRotatedFile(rotatedFiles: string[]) { + if (rotatedFiles.length < this.keepFiles) { + return; + } + + const lastFilePath: string = rotatedFiles.pop() as string; + await unlinkAsync(lastFilePath); + } + + async _renameRotatedFilesByOne(rotatedFiles: string[]) { + const logFileBaseName = basename(this.logFilePath); + const logFilesFolder = dirname(this.logFilePath); + + for (let i = rotatedFiles.length - 1; i >= 0; i--) { + const oldFilePath = rotatedFiles[i]; + const newFilePath = `${logFilesFolder}${sep}${logFileBaseName}.${i + 1}`; + await renameAsync(oldFilePath, newFilePath); + } + } + + async _rotateCurrentLogFile() { + const newFilePath = `${this.logFilePath}.0`; + await renameAsync(this.logFilePath, newFilePath); + } + + _sendReloadLogConfigSignal() { + if (isMaster) { + (process as NodeJS.EventEmitter).emit('SIGHUP'); + return; + } + + // Send a special message to the cluster manager + // so it can forward it correctly + // It will only run when we are under cluster mode (not under a production environment) + if (!process.send) { + this.log( + ['error', 'logging:rotate'], + 'For some unknown reason process.send is not defined, the rotation was not successful' + ); + return; + } + + process.send(['RELOAD_LOGGING_CONFIG_FROM_SERVER_WORKER']); + } +} From 6ea1b2cceef630b3e6ea4a84e24ba4d86d6b18e1 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 9 Dec 2019 18:46:45 -0700 Subject: [PATCH 30/36] [ftr/lifecycle] refactor to be typesafe (#52453) * [ftr/lifecycle] refactor to be typesafe * update test fixture --- .../development-functional-tests.asciidoc | 2 +- .../fixtures/failure_hooks/config.js | 23 +- .../functional_test_runner.ts | 24 +- .../src/functional_test_runner/lib/index.ts | 3 +- .../functional_test_runner/lib/lifecycle.ts | 78 ++----- .../lib/lifecycle_phase.test.ts | 206 ++++++++++++++++++ .../lib/lifecycle_phase.ts | 82 +++++++ .../lib/mocha/decorate_mocha_ui.js | 8 +- .../lib/mocha/run_tests.ts | 2 +- .../lib/mocha/setup_mocha.js | 2 +- .../services/kibana_server/kibana_server.ts | 2 +- test/functional/services/failure_debugging.ts | 3 +- .../services/remote/poll_for_log_entry.ts | 2 +- test/functional/services/remote/remote.ts | 12 +- test/functional/services/remote/webdriver.ts | 6 +- .../services/visual_testing/visual_testing.ts | 2 +- 16 files changed, 353 insertions(+), 104 deletions(-) create mode 100644 packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts create mode 100644 packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc index 350a3c2a997cf9..77a2bfe77b4ab5 100644 --- a/docs/developer/core/development-functional-tests.asciidoc +++ b/docs/developer/core/development-functional-tests.asciidoc @@ -282,7 +282,7 @@ The `FunctionalTestRunner` comes with three built-in services: * Source: {blob}src/functional_test_runner/lib/lifecycle.ts[src/functional_test_runner/lib/lifecycle.ts] * Designed primary for use in services * Exposes lifecycle events for basic coordination. Handlers can return a promise and resolve/fail asynchronously -* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup`, `phaseStart`, `phaseEnd` +* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup` [float] ===== Kibana Services diff --git a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js index 3ff674c89682d9..37ea49172d2c46 100644 --- a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js +++ b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js @@ -29,18 +29,19 @@ export default function () { services: { hookIntoLIfecycle({ getService }) { const log = getService('log'); + const lifecycle = getService('lifecycle') - getService('lifecycle') - .on('testFailure', async (err, test) => { - log.info('testFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); - }) - .on('testHookFailure', async (err, test) => { - log.info('testHookFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); - }); + lifecycle.testFailure.add(async (err, test) => { + log.info('testFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); + + lifecycle.testHookFailure.add(async (err, test) => { + log.info('testHookFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); } }, mochaReporter: { diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 459c52997e2297..fcba9691b1772f 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -18,10 +18,11 @@ */ import { ToolingLog } from '@kbn/dev-utils'; -import { Suite, Test } from './fake_mocha_types'; +import { Suite, Test } from './fake_mocha_types'; import { - createLifecycle, + Lifecycle, + LifecyclePhase, readConfigFile, ProviderCollection, readProviderSpec, @@ -31,7 +32,7 @@ import { } from './lib'; export class FunctionalTestRunner { - public readonly lifecycle = createLifecycle(); + public readonly lifecycle = new Lifecycle(); private closed = false; constructor( @@ -39,13 +40,12 @@ export class FunctionalTestRunner { private readonly configFile: string, private readonly configOverrides: any ) { - this.lifecycle.on('phaseStart', name => { - log.verbose('starting %j lifecycle phase', name); - }); - - this.lifecycle.on('phaseEnd', name => { - log.verbose('ending %j lifecycle phase', name); - }); + for (const [key, value] of Object.entries(this.lifecycle)) { + if (value instanceof LifecyclePhase) { + value.before$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + value.after$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + } + } } async run() { @@ -59,7 +59,7 @@ export class FunctionalTestRunner { await providers.loadAll(); const mocha = await setupMocha(this.lifecycle, this.log, config, providers); - await this.lifecycle.trigger('beforeTests'); + await this.lifecycle.beforeTests.trigger(); this.log.info('Starting tests'); return await runTests(this.lifecycle, mocha); @@ -140,6 +140,6 @@ export class FunctionalTestRunner { if (this.closed) return; this.closed = true; - await this.lifecycle.trigger('cleanup'); + await this.lifecycle.cleanup.trigger(); } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index 88995e9acc5fea..2d354938d76483 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -17,7 +17,8 @@ * under the License. */ -export { createLifecycle, Lifecycle } from './lifecycle'; +export { Lifecycle } from './lifecycle'; +export { LifecyclePhase } from './lifecycle_phase'; export { readConfigFile, Config } from './config'; export { readProviderSpec, ProviderCollection, Provider } from './providers'; export { runTests, setupMocha } from './mocha'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 2d9629a436b3a7..7f78bc28c6d3d9 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -17,64 +17,22 @@ * under the License. */ -import * as Rx from 'rxjs'; - -type Listener = (...args: any[]) => Promise | void; -export type Lifecycle = ReturnType; - -export function createLifecycle() { - const listeners = { - beforeLoadTests: [] as Listener[], - beforeTests: [] as Listener[], - beforeTestSuite: [] as Listener[], - beforeEachTest: [] as Listener[], - afterTestSuite: [] as Listener[], - testFailure: [] as Listener[], - testHookFailure: [] as Listener[], - cleanup: [] as Listener[], - phaseStart: [] as Listener[], - phaseEnd: [] as Listener[], - }; - - const cleanup$ = new Rx.ReplaySubject(1); - - return { - cleanup$: cleanup$.asObservable(), - - on(name: keyof typeof listeners, fn: Listener) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - listeners[name].push(fn); - return this; - }, - - async trigger(name: keyof typeof listeners, ...args: any[]) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - if (name === 'cleanup') { - if (cleanup$.closed) { - return; - } - - cleanup$.next(); - cleanup$.complete(); - } - - try { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseStart', name); - } - - await Promise.all(listeners[name].map(async fn => await fn(...args))); - } finally { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseEnd', name); - } - } - }, - }; +import { LifecyclePhase } from './lifecycle_phase'; + +// mocha's global types mean we can't import Mocha or it will override the global jest types.............. +type ItsASuite = any; +type ItsATest = any; + +export class Lifecycle { + public readonly beforeTests = new LifecyclePhase<[]>({ + singular: true, + }); + public readonly beforeTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly beforeEachTest = new LifecyclePhase<[ItsATest]>(); + public readonly afterTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly testFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly testHookFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly cleanup = new LifecyclePhase<[]>({ + singular: true, + }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts new file mode 100644 index 00000000000000..94dd76884f2cab --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts @@ -0,0 +1,206 @@ +/* + * 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 * as Rx from 'rxjs'; +import { materialize, toArray } from 'rxjs/operators'; + +import { LifecyclePhase } from './lifecycle_phase'; + +describe('with randomness', () => { + beforeEach(() => { + const randomOrder = [0, 0.75, 0.5, 0.25, 1]; + jest.spyOn(Math, 'random').mockImplementation(() => { + const n = randomOrder.shift()!; + randomOrder.push(n); + return n; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('calls handlers in random order', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + phase.add( + jest.fn(() => { + order.push('one'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('two'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('three'); + }) + ); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "one", + "three", + "two", + ] + `); + }); +}); + +describe('without randomness', () => { + beforeEach(() => jest.spyOn(Math, 'random').mockImplementation(() => 0)); + afterEach(() => jest.restoreAllMocks()); + + it('calls all handlers and throws first error', async () => { + const phase = new LifecyclePhase(); + const fn1 = jest.fn(); + phase.add(fn1); + + const fn2 = jest.fn(() => { + throw new Error('foo'); + }); + phase.add(fn2); + + const fn3 = jest.fn(); + phase.add(fn3); + + await expect(phase.trigger()).rejects.toThrowErrorMatchingInlineSnapshot(`"foo"`); + expect(fn1).toHaveBeenCalled(); + expect(fn2).toHaveBeenCalled(); + expect(fn3).toHaveBeenCalled(); + }); + + it('triggers before$ just before calling handler and after$ once it resolves', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + const beforeSub = jest.fn(() => order.push('before')); + phase.before$.subscribe(beforeSub); + + const afterSub = jest.fn(() => order.push('after')); + phase.after$.subscribe(afterSub); + + const handler = jest.fn(async () => { + order.push('handler start'); + await new Promise(resolve => setTimeout(resolve, 100)); + order.push('handler done'); + }); + phase.add(handler); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "before", + "handler start", + "handler done", + "after", + ] + `); + }); + + it('completes before$ and after$ if phase is singular', async () => { + const phase = new LifecyclePhase({ singular: true }); + + const beforeNotifs: Array> = []; + phase.before$.pipe(materialize()).subscribe(n => beforeNotifs.push(n)); + + const afterNotifs: Array> = []; + phase.after$.pipe(materialize()).subscribe(n => afterNotifs.push(n)); + + await phase.trigger(); + expect(beforeNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + expect(afterNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('completes before$ subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.before$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('replays after$ event subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.after$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); +}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts new file mode 100644 index 00000000000000..5c7fdb532faa1f --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts @@ -0,0 +1,82 @@ +/* + * 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 * as Rx from 'rxjs'; + +const shuffle = (arr: T[]) => arr.slice().sort(() => (Math.random() > 0.5 ? 1 : -1)); + +export type GetArgsType> = T extends LifecyclePhase + ? X + : never; + +export class LifecyclePhase { + private readonly handlers: Array<(...args: Args) => Promise | void> = []; + + private readonly beforeSubj = new Rx.Subject(); + public readonly before$ = this.beforeSubj.asObservable(); + + private readonly afterSubj = this.options.singular + ? new Rx.ReplaySubject(1) + : new Rx.Subject(); + public readonly after$ = this.afterSubj.asObservable(); + + constructor( + private readonly options: { + singular?: boolean; + } = {} + ) {} + + public add(fn: (...args: Args) => Promise | void) { + this.handlers.push(fn); + } + + public async trigger(...args: Args) { + if (this.beforeSubj.isStopped) { + throw new Error(`singular lifecycle event can only be triggered once`); + } + + this.beforeSubj.next(undefined); + if (this.options.singular) { + this.beforeSubj.complete(); + } + + // catch the first error but still execute all handlers + let error; + + // shuffle the handlers to prevent relying on their order + for (const fn of shuffle(this.handlers)) { + try { + await fn(...args); + } catch (_error) { + if (!error) { + error = _error; + } + } + } + + this.afterSubj.next(undefined); + if (this.options.singular) { + this.afterSubj.complete(); + } + + if (error) { + throw error; + } + } +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index e65eb2f27d0636..4eb45229c22346 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -58,7 +58,7 @@ export function decorateMochaUi(lifecycle, context) { argumentsList[1] = function() { before(async () => { - await lifecycle.trigger('beforeTestSuite', this); + await lifecycle.beforeTestSuite.trigger(this); }); this.tags = tags => { @@ -68,7 +68,7 @@ export function decorateMochaUi(lifecycle, context) { provider.call(this); after(async () => { - await lifecycle.trigger('afterTestSuite', this); + await lifecycle.afterTestSuite.trigger(this); }); }; @@ -94,7 +94,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testFailure', err, test); + await lifecycle.testFailure.trigger(err, test); }) ); } @@ -112,7 +112,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testHookFailure', err, test); + await lifecycle.testHookFailure.trigger(err, test); }) ); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts index 2d98052b1062a4..654f588fda8580 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts @@ -35,7 +35,7 @@ export async function runTests(lifecycle: Lifecycle, mocha: Mocha) { runComplete = true; }); - lifecycle.on('cleanup', () => { + lifecycle.cleanup.add(() => { if (!runComplete) runner.abort(); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index a425251a29f36f..326877919d9855 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -41,7 +41,7 @@ export async function setupMocha(lifecycle, log, config, providers) { // global beforeEach hook in root suite triggers before all others mocha.suite.beforeEach('global before each', async function() { - await lifecycle.trigger('beforeEachTest', this.currentTest); + await lifecycle.beforeEachTest.trigger(this.currentTest); }); loadTestFiles({ diff --git a/test/common/services/kibana_server/kibana_server.ts b/test/common/services/kibana_server/kibana_server.ts index f7b7885840455e..16039d6fee8335 100644 --- a/test/common/services/kibana_server/kibana_server.ts +++ b/test/common/services/kibana_server/kibana_server.ts @@ -32,7 +32,7 @@ export function KibanaServerProvider({ getService }: FtrProviderContext) { const kbn = new KbnClient(log, [url], defaults); if (defaults) { - lifecycle.on('beforeTests', async () => { + lifecycle.beforeTests.add(async () => { await kbn.uiSettings.update(defaults); }); } diff --git a/test/functional/services/failure_debugging.ts b/test/functional/services/failure_debugging.ts index e2be69d7797992..cd12f1b75c8281 100644 --- a/test/functional/services/failure_debugging.ts +++ b/test/functional/services/failure_debugging.ts @@ -65,5 +65,6 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex await Promise.all([screenshots.takeForFailure(name), logCurrentUrl(), savePageHtml(name)]); } - lifecycle.on('testFailure', onFailure).on('testHookFailure', onFailure); + lifecycle.testFailure.add(onFailure); + lifecycle.testHookFailure.add(onFailure); } diff --git a/test/functional/services/remote/poll_for_log_entry.ts b/test/functional/services/remote/poll_for_log_entry.ts index 71e2711906fce7..0bd5d94f892b0e 100644 --- a/test/functional/services/remote/poll_for_log_entry.ts +++ b/test/functional/services/remote/poll_for_log_entry.ts @@ -29,7 +29,7 @@ export function pollForLogEntry$( driver: WebDriver, type: string, ms: number, - stop$: Rx.Observable + stop$: Rx.Observable ) { const logCtrl = driver.manage().logs(); const poll$ = new Rx.BehaviorSubject(undefined); diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 380c33e93ad901..90ff55fbebde55 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -80,7 +80,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { driver, logging.Type.BROWSER, config.get('browser.logPollingMs'), - lifecycle.cleanup$ as any + lifecycle.cleanup.after$ ) .pipe( mergeMap(logEntry => { @@ -110,7 +110,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { } } - lifecycle.on('beforeTests', async () => { + lifecycle.beforeTests.add(async () => { // hard coded default, can be overridden per suite using `browser.setWindowSize()` // and will be automatically reverted after each suite await driver @@ -120,7 +120,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { }); const windowSizeStack: Array<{ width: number; height: number }> = []; - lifecycle.on('beforeTestSuite', async () => { + lifecycle.beforeTestSuite.add(async () => { windowSizeStack.unshift( await driver .manage() @@ -129,11 +129,11 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { ); }); - lifecycle.on('beforeEachTest', async () => { + lifecycle.beforeEachTest.add(async () => { await driver.manage().setTimeouts({ implicit: config.get('timeouts.find') }); }); - lifecycle.on('afterTestSuite', async () => { + lifecycle.afterTestSuite.add(async () => { const { width, height } = windowSizeStack.shift()!; await driver .manage() @@ -143,7 +143,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { await clearBrowserStorage('localStorage'); }); - lifecycle.on('cleanup', async () => { + lifecycle.cleanup.add(async () => { if (logSubscription) { await new Promise(r => logSubscription!.add(r)); } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 50303fbad7aab2..1a7abc63170753 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -115,9 +115,9 @@ async function attemptToCreateCommand( session, logging.Type.BROWSER, logPollingMs, - lifecycle.cleanup$ + lifecycle.cleanup.after$ ).pipe( - takeUntil(lifecycle.cleanup$), + takeUntil(lifecycle.cleanup.after$), map(({ message, level: { name: level } }) => ({ message: message.replace(/\\n/g, '\n'), level, @@ -151,7 +151,7 @@ async function attemptToCreateCommand( } const { input, chunk$, cleanup } = await createStdoutSocket(); - lifecycle.on('cleanup', cleanup); + lifecycle.cleanup.add(cleanup); const session = await new Builder() .forBrowser(browserType) diff --git a/test/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts index fd31a4d8b6e4fd..4ad97f8d987172 100644 --- a/test/visual_regression/services/visual_testing/visual_testing.ts +++ b/test/visual_regression/services/visual_testing/visual_testing.ts @@ -54,7 +54,7 @@ export async function VisualTestingProvider({ getService }: FtrProviderContext) const lifecycle = getService('lifecycle'); let currentTest: Test | undefined; - lifecycle.on('beforeEachTest', (test: Test) => { + lifecycle.beforeEachTest.add(test => { currentTest = test; }); From 5fb59f36a00b7379d36245a7eea1b8e93f953ab2 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 9 Dec 2019 20:50:25 -0700 Subject: [PATCH 31/36] [SIEM][Detection Engine] Fixes a bug with signalsIndex key for configuration ## Summary * Simple small bug fix so that the `signalsIndex` works again. Without this any developer starting up Kibana with their `xpack.siem.signalsIndex` set will get a quick crash. Test: Add this key to your `kibana.dev.yml` ```sh xpack.siem.signalsIndex: .siem-signals-your-name ``` Ensure it starts up without crashing. Take the key out, ensure it starts up without crashing. In the folder: ```sh detection_engine/scripts ``` You can run this and get back the index you expect which is whatever you set the key to and the space you're in: ```sh ./get_signal_index.sh { "name": ".siem-signals-your-name-default" } ``` ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ ~~- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~~ ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- x-pack/legacy/plugins/siem/index.ts | 5 ++++- x-pack/plugins/siem/server/config.ts | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index cb72fb4cfba464..cf9fffc6a14558 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -150,13 +150,16 @@ export const siem = (kibana: any) => { }, route: route.bind(server), }; - // @ts-ignore-next-line: setup.plugins is too loosely typed plugin(initializerContext).setup(setup.core, setup.plugins); initServerWithKibana(initializerContext, serverFacade); }, config(Joi: Root) { + // See x-pack/plugins/siem/server/config.ts if you're adding another + // value where the configuration has to be duplicated at the moment. + // When we move over to the new platform completely this will be + // removed and only server/config.ts should be used. return Joi.object() .keys({ enabled: Joi.boolean().default(true), diff --git a/x-pack/plugins/siem/server/config.ts b/x-pack/plugins/siem/server/config.ts index 8518177cf6f968..456646cc825f33 100644 --- a/x-pack/plugins/siem/server/config.ts +++ b/x-pack/plugins/siem/server/config.ts @@ -7,9 +7,14 @@ import { Observable } from 'rxjs'; import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from 'src/core/server'; +import { + SIGNALS_INDEX_KEY, + DEFAULT_SIGNALS_INDEX, +} from '../../../legacy/plugins/siem/common/constants'; export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), + [SIGNALS_INDEX_KEY]: schema.string({ defaultValue: DEFAULT_SIGNALS_INDEX }), }); export const createConfig$ = (context: PluginInitializerContext) => From 23edb41739b1f9b80e0a9b416618124b27c34ff1 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 9 Dec 2019 20:57:38 -0700 Subject: [PATCH 32/36] [SIEM][Detection Engine] Utilizes native alert tags ## Summary * Changes out the params of tags to use the native alert tags. * Updated unit tests * Updated examples Tests are: Post a query with a tag ```sh ./post_rule.sh ./rules/queries/query_with_tags.json ``` Filter by that tag: ```sh ./find_rule_by_filter.sh "alert.attributes.tags:tag_1" ``` Update a query with a tag: ```sh ./update_rule.sh ./rules/updates/update_tags.json ``` ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../alerts/__mocks__/es_results.ts | 3 +- .../detection_engine/alerts/create_rules.ts | 3 +- .../alerts/rules_alert_type.ts | 3 +- .../lib/detection_engine/alerts/types.ts | 2 +- .../detection_engine/alerts/update_rules.ts | 4 +- .../lib/detection_engine/alerts/utils.test.ts | 37 ++++++++++++++----- .../lib/detection_engine/alerts/utils.ts | 15 +++++++- .../lib/detection_engine/routes/utils.ts | 2 +- .../scripts/find_rule_by_filter.sh | 1 + .../rules/queries/query_with_tags.json | 12 ++++++ .../scripts/rules/updates/update_tags.json | 4 ++ 11 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts index 435a8d9bf86881..4c113544e6e215 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts @@ -22,7 +22,6 @@ export const sampleRuleAlertParams = ( index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], type: 'query', from: 'now-6m', - tags: ['some fake tag'], to: 'now', severity: 'high', query: 'user.name: root or user.name: admin', @@ -277,7 +276,7 @@ export const sampleRule = (): Partial => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts index e673bb116e1dd9..4cbf3756f58ac1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts @@ -37,7 +37,7 @@ export const createRules = async ({ return alertsClient.create({ data: { name, - tags: [], + tags, alertTypeId: SIGNALS_ID, params: { description, @@ -55,7 +55,6 @@ export const createRules = async ({ maxSignals, riskScore, severity, - tags, threats, to, type, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts index 488c34c945b484..9823d8b3b9beac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts @@ -46,7 +46,6 @@ export const rulesAlertType = ({ maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }), riskScore: schema.number(), severity: schema.string(), - tags: schema.arrayOf(schema.string(), { defaultValue: [] }), threats: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))), to: schema.string(), type: schema.string(), @@ -70,6 +69,7 @@ export const rulesAlertType = ({ // TODO: Remove this hard extraction of name once this is fixed: https://github.com/elastic/kibana/issues/50522 const savedObject = await services.savedObjectsClient.get('alert', alertId); const name: string = savedObject.attributes.name; + const tags: string[] = savedObject.attributes.tags; const createdBy: string = savedObject.attributes.createdBy; const updatedBy: string = savedObject.attributes.updatedBy; @@ -134,6 +134,7 @@ export const rulesAlertType = ({ interval, enabled, pageSize: searchAfterSize, + tags, }); if (bulkIndexResult) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index 7f61765f5532cc..c9d265ebffacd1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -148,7 +148,7 @@ export interface ReadRuleByRuleId { ruleId: string; } -export type RuleTypeParams = Omit; +export type RuleTypeParams = Omit; export type RuleAlertType = Alert & { id: string; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts index a2d49b88a8f643..2eaa05ae2fa6ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts @@ -99,7 +99,6 @@ export const updateRules = async ({ maxSignals, riskScore, severity, - tags, threats, to, type, @@ -112,11 +111,10 @@ export const updateRules = async ({ } else if (!rule.enabled && enabled) { await alertsClient.enable({ id: rule.id }); } - return alertsClient.update({ id: rule.id, data: { - tags: [], + tags: tags != null ? tags : [], name: calculateName({ updatedName: name, originalName: rule.name }), interval: calculateInterval(interval, rule.interval), actions, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts index 83bf509fa7a937..41052ab4bbb159 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts @@ -68,6 +68,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -102,7 +103,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -131,6 +132,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -174,7 +176,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -202,6 +204,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -244,7 +247,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -270,6 +273,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -307,7 +311,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -448,6 +452,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -475,6 +480,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -494,6 +500,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -513,6 +520,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(successfulsingleBulkCreate).toEqual(true); @@ -583,6 +591,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(result).toEqual(true); @@ -634,6 +643,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(result).toEqual(true); @@ -656,6 +666,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -685,6 +696,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -714,6 +726,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -745,6 +758,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -776,6 +790,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -809,6 +824,7 @@ describe('utils', () => { enabled: true, pageSize: 1, filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(false); }); @@ -884,7 +900,7 @@ describe('utils', () => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }, @@ -937,7 +953,7 @@ describe('utils', () => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }, @@ -968,6 +984,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -988,7 +1005,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', @@ -1018,6 +1035,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -1038,7 +1056,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', @@ -1057,6 +1075,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -1077,7 +1096,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index a7668f47b614c4..1787aa3a3081be 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -26,6 +26,7 @@ interface BuildRuleParams { createdBy: string; updatedBy: string; interval: string; + tags: string[]; } export const buildRule = ({ @@ -36,6 +37,7 @@ export const buildRule = ({ createdBy, updatedBy, interval, + tags, }: BuildRuleParams): Partial => { return pickBy((value: unknown) => value != null, { id, @@ -56,7 +58,7 @@ export const buildRule = ({ query: ruleParams.query, references: ruleParams.references, severity: ruleParams.severity, - tags: ruleParams.tags, + tags, type: ruleParams.type, to: ruleParams.to, enabled, @@ -94,6 +96,7 @@ interface BuildBulkBodyParams { updatedBy: string; interval: string; enabled: boolean; + tags: string[]; } export const buildEventTypeSignal = (doc: SignalSourceHit): object => { @@ -114,6 +117,7 @@ export const buildBulkBody = ({ updatedBy, interval, enabled, + tags, }: BuildBulkBodyParams): SignalHit => { const rule = buildRule({ ruleParams, @@ -123,6 +127,7 @@ export const buildBulkBody = ({ createdBy, updatedBy, interval, + tags, }); const signal = buildSignal(doc, rule); const event = buildEventTypeSignal(doc); @@ -147,6 +152,7 @@ interface SingleBulkCreateParams { updatedBy: string; interval: string; enabled: boolean; + tags: string[]; } export const generateId = ( @@ -172,6 +178,7 @@ export const singleBulkCreate = async ({ updatedBy, interval, enabled, + tags, }: SingleBulkCreateParams): Promise => { if (someResult.hits.hits.length === 0) { return true; @@ -197,7 +204,7 @@ export const singleBulkCreate = async ({ ), }, }, - buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled }), + buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), ]); const time1 = performance.now(); const firstResult: BulkResponse = await services.callCluster('bulk', { @@ -291,6 +298,7 @@ interface SearchAfterAndBulkCreateParams { enabled: boolean; pageSize: number; filter: unknown; + tags: string[]; } // search_after through documents and re-index using bulk endpoint. @@ -308,6 +316,7 @@ export const searchAfterAndBulkCreate = async ({ interval, enabled, pageSize, + tags, }: SearchAfterAndBulkCreateParams): Promise => { if (someResult.hits.hits.length === 0) { return true; @@ -326,6 +335,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + tags, }); const totalHits = typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; @@ -385,6 +395,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + tags, }); logger.debug('finished next bulk index'); } catch (exc) { 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 0d15fa1faa78f9..6df4174e628b3c 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 @@ -52,7 +52,7 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial Date: Tue, 10 Dec 2019 13:50:45 +0300 Subject: [PATCH 33/36] [Visualize] Shim with local application service (#49891) * Add dashboard updates * Use I18nProvider instead of I18nContext * remove unused dependencies * Centralizing and cleaning up legacy imports * Fix merge conflict * fix merge bugs and rename main dynamic entrypoint * Rename app to legacy_app * Clear deps * fix jest tests * fix saved object finder bug * Fix unit tests * Ignore TS * revert using stateless component for this PR * fix types * Fix merge conflicts * Update deps * Revert filter bar export * Revert ts-ignore * Clean up * Refactoring * Fix test * Remove global_state_sync * Refactoring * Remove uiExports/embeddableFactories * Trigger digest cycle in local angular when vis is changed. * Fix TS * Revert back syncOnMount * Add missed import * Revert import 'uiExports/embeddableFactories' * Update app navigation func test * Update app navigation func test * Update app navigation func test * Remove 'kibana-install-dir' arg in pluginFunctionalTestsRelease * Fix review comments * Fix code review comments * Rename alias * Fix indexPatterns * Use IndexPatternsContract interface --- src/legacy/core_plugins/kibana/index.js | 1 + .../public/dashboard/dashboard_app.html | 2 +- .../core_plugins/kibana/public/kibana.js | 1 - .../kibana/public/visualize/application.ts | 193 ++++++++++++++++++ .../public/visualize/editor/editor.html | 8 +- .../kibana/public/visualize/editor/editor.js | 177 ++++++---------- .../public/visualize/editor/visualization.js | 12 +- .../visualize/editor/visualization_editor.js | 15 +- .../embeddable/disabled_lab_embeddable.tsx | 2 +- .../visualize/embeddable/get_index_pattern.ts | 14 +- .../embeddable/visualize_embeddable.ts | 2 + .../visualize_embeddable_factory.tsx | 77 ++++--- .../public/visualize/global_state_sync.ts | 67 ++++++ .../visualize/help_menu/help_menu_util.js | 4 +- .../kibana/public/visualize/index.js | 89 -------- .../kibana/public/visualize/index.ts | 70 +++++++ .../public/visualize/kibana_services.ts | 145 +++++-------- .../kibana/public/visualize/legacy_app.js | 169 +++++++++++++++ .../kibana/public/visualize/legacy_imports.ts | 76 +++++++ .../visualize/listing/visualize_listing.html | 2 + .../visualize/listing/visualize_listing.js | 68 +++--- .../listing/visualize_listing_table.js | 7 +- .../kibana/public/visualize/plugin.ts | 161 +++++++++++++++ .../kibana/public/visualize/types.d.ts | 2 +- .../kibana/public/visualize/visualize_app.ts | 31 +++ .../__snapshots__/new_vis_modal.test.tsx.snap | 40 ++++ .../visualize/wizard/new_vis_modal.test.tsx | 85 +++++--- .../public/visualize/wizard/new_vis_modal.tsx | 14 +- .../search_selection/search_selection.tsx | 4 +- .../public/visualize/wizard/show_new_vis.tsx | 13 +- .../type_selection/new_vis_help.test.tsx | 13 +- .../wizard/type_selection/new_vis_help.tsx | 5 +- .../wizard/type_selection/type_selection.tsx | 4 +- .../ui/public/vis/editors/default/default.js | 9 +- .../ui/ui_exports/ui_export_defaults.js | 6 - tasks/config/run.js | 1 - .../plugins/canvas/public/legacy_start.ts | 1 - .../dashboard_mode/public/dashboard_viewer.js | 1 - 38 files changed, 1115 insertions(+), 476 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/application.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/index.js create mode 100644 src/legacy/core_plugins/kibana/public/visualize/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/legacy_app.js create mode 100644 src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/plugin.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 33e820a5300c60..659ca36d840906 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -62,6 +62,7 @@ export default function (kibana) { hacks: [ 'plugins/kibana/discover', 'plugins/kibana/dev_tools', + 'plugins/kibana/visualize', ], savedObjectTypes: [ 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 4f41ab5d4fad6b..c7fd8600b73bba 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -35,7 +35,7 @@ --> { + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); + // custom routing stuff + initVisualizeApp(angularModuleInstance, deps); + } + const $injector = mountVisualizeApp(appBasePath, element); + return () => $injector.get('$rootScope').$destroy(); +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/visualize'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountVisualizeApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalConfirmModalModule(); + + const visualizeAngularModule: IModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'app/visualize/Config', + 'app/visualize/I18n', + 'app/visualize/Private', + 'app/visualize/PersistedState', + 'app/visualize/TopNav', + 'app/visualize/State', + 'app/visualize/ConfirmModal', + ]); + return visualizeAngularModule; +} + +function createLocalConfirmModalModule() { + angular + .module('app/visualize/ConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalStateModule() { + angular + .module('app/visualize/State', [ + 'app/visualize/Private', + 'app/visualize/Config', + 'app/visualize/KbnUrl', + 'app/visualize/Promise', + 'app/visualize/PersistedState', + ]) + .factory('AppState', function(Private: IPrivate) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: IPrivate) { + return Private(AppStateProvider).getAppState; + }) + .service('globalState', function(Private: IPrivate) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('app/visualize/PersistedState', ['app/visualize/Private', 'app/visualize/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('app/visualize/Config', ['app/visualize/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('app/visualize/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('app/visualize/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalI18nModule() { + angular + .module('app/visualize/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html index bf9ac9b9bbe361..6190b92c9be3e1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html @@ -39,7 +39,7 @@ show-search-bar="true" show-query-bar="true" show-query-input="showQueryInput()" - show-filter-bar="showFilterBar() && chrome.getVisible()" + show-filter-bar="showFilterBar() && isVisible" show-date-picker="showQueryBarTimePicker()" show-auto-refresh-only="!showQueryBarTimePicker()" query="state.query" @@ -67,7 +67,7 @@ --> savedVisualizations.get($route.current.params)) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); - } - return savedVis; - }) - .catch(redirectWhenMissing({ - '*': '/visualize' - })); - } - } - }) - .when(`${VisualizeConstants.EDIT_PATH}/:id`, { - template: editorTemplate, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(core, npData, $rootScope, kbnUrl) - .then(() => savedVisualizations.get($route.current.params.id)) - .then((savedVis) => { - chrome.recentlyAccessed.add( - savedVis.getFullPath(), - savedVis.title, - savedVis.id - ); - return savedVis; - }) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis).catch(() => savedVis); - } - return savedVis; - }) - .catch( - redirectWhenMissing({ - visualization: '/visualize', - search: '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern': - '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern-field': - '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - }) - ); - } - } - }); +import { getServices } from '../kibana_services'; -uiModules - .get('app/visualize', [ - 'kibana/url' - ]) - .directive('visualizeApp', function () { +export function initEditorDirective(app, deps) { + app.directive('visualizeApp', function () { return { restrict: 'E', controllerAs: 'visualizeApp', - controller: VisEditor, + controller: VisualizeAppController, }; }); -function VisEditor( + initVisEditorDirective(app, deps); + initVisualizationDirective(app, deps); +} + +function VisualizeAppController( $scope, $element, $route, @@ -154,19 +70,42 @@ function VisEditor( $window, $injector, $timeout, - indexPatterns, kbnUrl, redirectWhenMissing, - Private, Promise, - config, kbnBaseUrl, - localStorage, + getAppState, + globalState, ) { - const queryFilter = Private(FilterBarQueryFilterProvider); - + const { + indexPatterns, + localStorage, + visualizeCapabilities, + share, + data: { + query: { + filterManager, + timefilter: { timefilter }, + }, + }, + toastNotifications, + legacyChrome, + chrome, + getBasePath, + core: { docLinks }, + savedQueryService, + uiSettings, + } = getServices(); + + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); + const queryFilter = filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; + const _applyVis = () => { + $scope.$apply(); + }; + // This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js. + savedVis.vis.on('apply', _applyVis); // vis is instance of src/legacy/ui/public/vis/vis.js. // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { vis, searchSource } = savedVis; @@ -177,7 +116,7 @@ function VisEditor( dirty: !savedVis.id }; - $scope.topNavMenu = [...(capabilities.visualize.save ? [{ + $scope.topNavMenu = [...(visualizeCapabilities.save ? [{ id: 'save', label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { @@ -246,7 +185,7 @@ function VisEditor( share.toggleShareContextMenu({ anchorElement, allowEmbed: true, - allowShortUrl: capabilities.visualize.createShortUrl, + allowShortUrl: visualizeCapabilities.createShortUrl, shareableUrl: unhashUrl(window.location.href), objectId: savedVis.id, objectType: 'visualization', @@ -295,7 +234,7 @@ function VisEditor( let stateMonitor; if (savedVis.id) { - docTitle.change(savedVis.title); + chrome.docTitle.change(savedVis.title); } // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. @@ -306,7 +245,7 @@ function VisEditor( linked: !!savedVis.savedSearchId, query: searchSource.getOwnField('query') || { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }, filters: searchSource.getOwnField('filter') || [], vis: savedVisState @@ -345,9 +284,9 @@ function VisEditor( queryFilter.setFilters(filters); }; - $scope.showSaveQuery = capabilities.visualize.saveQuery; + $scope.showSaveQuery = visualizeCapabilities.saveQuery; - $scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => { + $scope.$watch(() => visualizeCapabilities.saveQuery, (newCapability) => { $scope.showSaveQuery = newCapability; }); @@ -455,13 +394,15 @@ function VisEditor( } })); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', () => { if ($scope._handler) { $scope._handler.destroy(); } savedVis.destroy(); stateMonitor.destroy(); + filterStateManager.destroy(); subscriptions.unsubscribe(); + $scope.vis.off('apply', _applyVis); }); @@ -503,7 +444,7 @@ function VisEditor( delete $state.savedQuery; $state.query = { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }; queryFilter.removeAll(); $state.save(); @@ -589,14 +530,14 @@ function VisEditor( // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved // url, not the unsaved one. - chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + legacyChrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); kbnUrl.change(dashboardParsedUrl.appPath); } else if (savedVis.id === $route.current.params.id) { - docTitle.change(savedVis.lastSavedTitle); + chrome.docTitle.change(savedVis.lastSavedTitle); chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); savedVis.vis.title = savedVis.title; savedVis.vis.description = savedVis.description; @@ -661,7 +602,7 @@ function VisEditor( vis.type.feedbackMessage; }; - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); init(); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js index 198fbe19a0b7aa..d3651735c1a1d8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js @@ -17,13 +17,8 @@ * under the License. */ -import { getServices } from '../kibana_services'; - -const { embeddables, uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEmbedded', function (Private, $timeout, getAppState) { +export function initVisualizationDirective(app, deps) { + app.directive('visualizationEmbedded', function ($timeout, getAppState) { return { restrict: 'E', @@ -37,7 +32,7 @@ uiModules link: function ($scope, element) { $scope.renderFunction = async () => { if (!$scope._handler) { - $scope._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { + $scope._handler = await deps.embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { timeRange: $scope.timeRange, filters: $scope.filters || [], query: $scope.query, @@ -66,3 +61,4 @@ uiModules } }; }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index ead77e6bc41d50..bc6d4d4c484665 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -17,15 +17,8 @@ * under the License. */ -import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; - -const { uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEditor', function (Private, $timeout, getAppState) { - const editorTypes = Private(VisEditorTypesRegistryProvider); - +export function initVisEditorDirective(app, deps) { + app.directive('visualizationEditor', function ($timeout, getAppState) { return { restrict: 'E', scope: { @@ -38,7 +31,8 @@ uiModules link: function ($scope, element) { const editorType = $scope.savedObj.vis.type.editor; const Editor = typeof editorType === 'function' ? editorType : - editorTypes.find(editor => editor.key === editorType); + deps.editorTypes.find(editor => editor.key === editorType); + const editor = new Editor(element[0], $scope.savedObj); $scope.renderFunction = () => { @@ -62,3 +56,4 @@ uiModules } }; }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx index 065feae0455972..d8792a761b1860 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx @@ -19,8 +19,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { Embeddable, EmbeddableOutput } from '../../../../../../plugins/embeddable/public'; -import { Embeddable, EmbeddableOutput } from '../kibana_services'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 9bc9ab99c4aff2..7fe3678bb1f770 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,11 +17,10 @@ * under the License. */ -import { getServices, getFromSavedObject, VisSavedObject } from '../kibana_services'; +import { npStart } from 'ui/new_platform'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; - -const { savedObjectsClient, uiSettings } = getServices(); +import { VisSavedObject } from './visualize_embeddable'; +import { indexPatterns, IIndexPattern } from '../../../../../../plugins/data/public'; export async function getIndexPattern( savedVis: VisSavedObject @@ -30,7 +29,8 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const defaultIndex = uiSettings.get('defaultIndex'); + const savedObjectsClient = npStart.core.savedObjects.client; + const defaultIndex = npStart.core.uiSettings.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ @@ -39,10 +39,10 @@ export async function getIndexPattern( search: `"${savedVis.vis.params.index_pattern}"`, searchFields: ['title'], }); - const [indexPattern] = indexPatternObjects.savedObjects.map(getFromSavedObject); + const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject); return indexPattern; } const savedObject = await savedObjectsClient.get('index-pattern', defaultIndex); - return getFromSavedObject(savedObject); + return indexPatterns.getFromSavedObject(savedObject); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index e5a723a99eafd8..7ab60f8867c38d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -375,6 +375,8 @@ export class VisualizeEmbeddable extends Embeddable { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 15ad9a33232ef5..7c9efa280c9f15 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -17,36 +17,48 @@ * under the License. */ +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableActions'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; + import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; + import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; +import { + EmbeddableFactory, + ErrorEmbeddable, + Container, + EmbeddableOutput, +} from '../../../../../../plugins/embeddable/public'; +import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { showNewVisModal } from '../wizard'; import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; -import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; -import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; - import { - getServices, - Container, - EmbeddableFactory, - EmbeddableOutput, - ErrorEmbeddable, + VisualizeEmbeddable, + VisualizeInput, + VisualizeOutput, VisSavedObject, -} from '../kibana_services'; - -const { - addBasePath, - capabilities, - embeddable, - getInjector, - uiSettings, - visualizations, -} = getServices(); +} from './visualize_embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; +import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -96,7 +108,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (uiSettings.get('visualize:enableLabs')) { + if (npStart.core.uiSettings.get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; @@ -108,7 +120,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.visualize.save as boolean; + return npStart.core.application.capabilities.visualize.save as boolean; } public getDisplayName() { @@ -122,14 +134,16 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); + const $injector = await chrome.dangerouslyGetActiveInjector(); const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); try { const visId = savedObject.id as string; - const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; + const editUrl = visId + ? npStart.core.http.basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) + : ''; const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { @@ -161,7 +175,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); + const $injector = await chrome.dangerouslyGetActiveInjector(); const savedVisualizations = $injector.get('savedVisualizations'); try { @@ -179,14 +193,15 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. if (this.visTypes) { - showNewVisModal(this.visTypes, { - editorParams: ['addToDashboard'], - }); + showNewVisModal( + this.visTypes, + { + editorParams: ['addToDashboard'], + }, + npStart.core.http.basePath.prepend, + npStart.core.uiSettings + ); } return undefined; } } - -VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { - embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts new file mode 100644 index 00000000000000..71156bc38d4980 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -0,0 +1,67 @@ +/* + * 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 { State } from './legacy_imports'; +import { DataPublicPluginStart as DataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: DataStart +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = timefilter.getRefreshInterval(); + } + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js index d27003f39d4c08..9c00947d7663c9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js @@ -18,10 +18,8 @@ */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; -const { docLinks } = getServices(); -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome, docLinks) { chrome.setHelpExtension({ appName: i18n.translate('kbn.visualize.helpMenu.appName', { defaultMessage: 'Visualize', diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js deleted file mode 100644 index d42c72f67f8158..00000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ /dev/null @@ -1,89 +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 { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -import './editor/editor'; -import { i18n } from '@kbn/i18n'; -import './saved_visualizations/_saved_vis'; -import './saved_visualizations/saved_visualizations'; -import visualizeListingTemplate from './listing/visualize_listing.html'; -import { VisualizeListingController } from './listing/visualize_listing'; -import { VisualizeConstants } from './visualize_constants'; -import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs'; - -import { getServices, FeatureCatalogueCategory } from './kibana_services'; - -const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); - -uiRoutes - .defaults(/visualize/, { - requireUICapability: 'visualize.show', - badge: uiCapabilities => { - if (uiCapabilities.visualize.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.visualize.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save visualizations', - }), - iconType: 'glasses' - }; - } - }) - .when(VisualizeConstants.LANDING_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getLandingBreadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().npData, $rootScope, kbnUrl) - }, - }) - .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getWizardStep1Breadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().npData, $rootScope, kbnUrl) - }, - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate( - 'kbn.visualize.visualizeDescription', - { - defaultMessage: 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - } - ), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts new file mode 100644 index 00000000000000..5e9f2fdeb89991 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -0,0 +1,70 @@ +/* + * 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 'ui/collapsible_sidebar'; // used in default editor +import 'ui/vis/editors/default/sidebar'; + +import { + IPrivate, + legacyChrome, + npSetup, + npStart, + SavedObjectRegistryProvider, + VisEditorTypesRegistryProvider, +} from './legacy_imports'; +import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await legacyChrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const editorTypes = Private(VisEditorTypesRegistryProvider); + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + + return { + legacyChrome, + editorTypes, + savedObjectRegistry, + savedVisualizations: injector.get('savedVisualizations'), + }; +} + +(() => { + const instance = new VisualizePlugin(); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + embeddables, + navigation, + visualizations, + }); +})(); 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 40d36dab227fae..36a9ecf3fcf8c9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -17,108 +17,59 @@ * under the License. */ -import 'angular-sanitize'; // used in visualization_editor.js and visualization.js -import 'ui/collapsible_sidebar'; // used in default editor -import 'ui/vis/editors/default/sidebar'; -// load directives -import '../../../data/public'; +import { + ChromeStart, + LegacyCoreStart, + SavedObjectsClientContract, + ToastsStart, + IUiSettingsClient, +} from 'kibana/public'; -import { npStart } from 'ui/new_platform'; -import angular from 'angular'; // just used in editor.js -import chromeLegacy from 'ui/chrome'; +import { NavigationStart } from '../../../navigation/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { DataPublicPluginStart, IndexPatternsContract } from '../../../../../plugins/data/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { SavedVisualizations } from './types'; -import uiRoutes from 'ui/routes'; - -// @ts-ignore -import { docTitle } from 'ui/doc_title'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; - -// Saved objects -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -// @ts-ignore -import { SavedObject, SavedObjectProvider } from 'ui/saved_objects/saved_object'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; - -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { start as embeddables } from '../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; -import { start as data } from '../../../data/public/legacy'; - -const services = { - // new platform - addBasePath: npStart.core.http.basePath.prepend, - capabilities: npStart.core.application.capabilities, - chrome: npStart.core.chrome, - docLinks: npStart.core.docLinks, - embeddable: npStart.plugins.embeddable, - getBasePath: npStart.core.http.basePath.get, - savedObjectsClient: npStart.core.savedObjects.client, - toastNotifications: npStart.core.notifications.toasts, - uiSettings: npStart.core.uiSettings, - core: npStart.core, - - share: npStart.plugins.share, - npData: npStart.plugins.data, - data, - embeddables, - visualizations, - - // legacy - chromeLegacy, - docTitle, - FeatureCatalogueRegistryProvider, - FilterBarQueryFilterProvider, - getInjector: () => { - return chromeLegacy.dangerouslyGetActiveInjector(); - }, - SavedObjectProvider, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - timefilter: npStart.plugins.data.query.timefilter.timefilter, - uiModules, - uiRoutes, - wrapInI18nContext, +export interface VisualizeKibanaServices { + addBasePath: (url: string) => string; + chrome: ChromeStart; + core: LegacyCoreStart; + data: DataPublicPluginStart; + editorTypes: any; + embeddables: IEmbeddableStart; + getBasePath: () => string; + indexPatterns: IndexPatternsContract; + legacyChrome: any; + localStorage: Storage; + navigation: NavigationStart; + toastNotifications: ToastsStart; + savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; + savedQueryService: DataPublicPluginStart['query']['savedQueries']; + savedVisualizations: SavedVisualizations; + share: SharePluginStart; + uiSettings: IUiSettingsClient; + visualizeCapabilities: any; + visualizations: VisualizationsStart; +} - createUiStatsReporter, -}; +let services: VisualizeKibanaServices | null = null; +export function setServices(newServices: VisualizeKibanaServices) { + services = newServices; +} export function getServices() { + if (!services) { + throw new Error( + 'Kibana services not set - are you trying to import this module from outside of the visualize app?' + ); + } return services; } -// export legacy static dependencies -export { angular }; -export { getFromSavedObject } from 'ui/index_patterns'; -export { PersistedState } from 'ui/persisted_state'; -// @ts-ignore -export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; -export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; -export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; -export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; -export { - Container, - Embeddable, - EmbeddableFactory, - EmbeddableInput, - EmbeddableOutput, - ErrorEmbeddable, -} from '../../../../../plugins/embeddable/public'; - -// export types -export { METRIC_TYPE }; -export { AppState } from 'ui/state_management/app_state'; -export { VisType } from 'ui/vis'; - -// export const -export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; - -export { VisSavedObject } from './embeddable/visualize_embeddable'; +export function clearServices() { + services = null; +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js new file mode 100644 index 00000000000000..f47552e99a5c76 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js @@ -0,0 +1,169 @@ +/* + * 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 { find } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import editorTemplate from './editor/editor.html'; +import visualizeListingTemplate from './listing/visualize_listing.html'; + +import { initVisualizeAppDirective } from './visualize_app'; +import { VisualizeConstants } from './visualize_constants'; +import { VisualizeListingController } from './listing/visualize_listing'; +import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory } from './legacy_imports'; +import { syncOnMount } from './global_state_sync'; + +import { + getLandingBreadcrumbs, + getWizardStep1Breadcrumbs, + getCreateBreadcrumbs, + getEditBreadcrumbs +} from './breadcrumbs'; + +export function initVisualizeApp(app, deps) { + initVisualizeAppDirective(app, deps); + + app.run(globalState => { + syncOnMount(globalState, deps.data); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.data.query.timefilter.timefilter, + globalState, + $rootScope + ); + }); + + app.config(function ($routeProvider) { + const defaults = { + reloadOnSearch: false, + requireUICapability: 'visualize.show', + badge: () => { + if (deps.visualizeCapabilities.save) { + return undefined; + } + + return { + text: i18n.translate('kbn.visualize.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save visualizations', + }), + iconType: 'glasses', + }; + }, + }; + + $routeProvider + .when(VisualizeConstants.LANDING_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getLandingBreadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => false, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getWizardStep1Breadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => true, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.CREATE_PATH, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getCreateBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { core, data, savedVisualizations, visualizations } = deps; + const visTypes = visualizations.types.all(); + const visType = find(visTypes, { name: $route.current.params.type }); + const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; + const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId; + if (shouldHaveIndex && !hasIndex) { + throw new Error( + i18n.translate('kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage', { + defaultMessage: 'You must provide either an indexPattern or a savedSearchId', + }) + ); + } + + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis) + .catch(() => savedVis); + } + return savedVis; + }) + .catch(redirectWhenMissing({ + '*': '/visualize' + })); + } + } + }) + .when(`${VisualizeConstants.EDIT_PATH}/:id`, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { chrome, core, data, savedVisualizations } = deps; + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) + .then(() => savedVisualizations.get($route.current.params.id)) + .then(savedVis => { + chrome.recentlyAccessed.add( + savedVis.getFullPath(), + savedVis.title, + savedVis.id + ); + return savedVis; + }) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis).catch(() => savedVis); + } + return savedVis; + }) + .catch( + redirectWhenMissing({ + 'visualization': '/visualize', + 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id + }) + ); + } + } + }) + .when(`visualize/:tail*?`, { + redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + }); + }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts new file mode 100644 index 00000000000000..6adcfd2cc71864 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -0,0 +1,76 @@ +/* + * 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. + */ + +/** + * The imports in this file are static functions and types which still live in legacy folders and are used + * within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually + * this list should become empty. Imports from the top level of shimmed or moved plugins can be imported + * directly where they are needed. + */ + +import chrome from 'ui/chrome'; + +export const legacyChrome = chrome; + +// @ts-ignore +export { AppState, AppStateProvider } from 'ui/state_management/app_state'; +export { State } from 'ui/state_management/state'; +// @ts-ignore +export { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { PersistedState } from 'ui/persisted_state'; + +export { npSetup, npStart } from 'ui/new_platform'; +export { IPrivate } from 'ui/private'; +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; + +export { SavedObjectRegistryProvider } from 'ui/saved_objects'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; + +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +// @ts-ignore +export { EventsProvider } from 'ui/events'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +export { confirmModalFactory } from 'ui/modals/confirm_modal'; +export { configureAppAngularModule, ensureDefaultIndexPattern } from 'ui/legacy_compat'; +export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; +// @ts-ignore +export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; + +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; +export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; + +// @ts-ignore +export { defaultEditor } from 'ui/vis/editors/default/default'; +export { VisType } from 'ui/vis'; +export { wrapInI18nContext } from 'ui/i18n'; + +export { VisSavedObject } from './embeddable/visualize_embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html index edb7cccbd46a2f..4511ac61f7396c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -14,6 +14,8 @@ is-open="listingController.showNewVisModal" on-close="listingController.closeNewVisModal" vis-types-registry="listingController.visTypeRegistry" + add-base-path="listingController.addBasePath" + ui-settings="listingController.uiSettings" >
diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index f9e3a1a90115a3..9b02be0581b8d6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -24,36 +24,46 @@ import { VisualizeConstants } from '../visualize_constants'; import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; - -const { - addBasePath, - chrome, - chromeLegacy, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - timefilter, - toastNotifications, - uiModules, - wrapInI18nContext, - visualizations, -} = getServices(); - -const app = uiModules.get('app/visualize', ['ngRoute', 'react']); -app.directive('visualizeListingTable', reactDirective => - reactDirective(wrapInI18nContext(VisualizeListingTable)) -); -app.directive('newVisModal', reactDirective => reactDirective(wrapInI18nContext(NewVisModal))); +import { wrapInI18nContext } from '../legacy_imports'; + +export function initListingDirective(app) { + app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable))); + app.directive('newVisModal', reactDirective => + reactDirective(wrapInI18nContext(NewVisModal), [ + ['visTypesRegistry', { watchDepth: 'collection' }], + ['onClose', { watchDepth: 'reference' }], + ['addBasePath', { watchDepth: 'reference' }], + ['uiSettings', { watchDepth: 'reference' }], + 'isOpen', + ]) + ); +} export function VisualizeListingController($injector, createNewVis) { - const Private = $injector.get('Private'); - const config = $injector.get('config'); + const { + addBasePath, + chrome, + legacyChrome, + savedObjectRegistry, + savedObjectsClient, + data: { + query: { + timefilter: { timefilter }, + }, + }, + toastNotifications, + uiSettings, + visualizations, + core: { docLinks }, + } = getServices(); const kbnUrl = $injector.get('kbnUrl'); - const savedObjectClient = Private(SavedObjectsClientProvider); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); this.showNewVisModal = false; + this.addBasePath = addBasePath; + this.uiSettings = uiSettings; this.createNewVis = () => { this.showNewVisModal = true; @@ -82,14 +92,14 @@ export function VisualizeListingController($injector, createNewVis) { } // TODO: Extract this into an external service. - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; + const services = savedObjectRegistry.byLoaderPropertiesName; const visualizationService = services.visualizations; this.visTypeRegistry = visualizations.types; this.fetchItems = filter => { - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = uiSettings.get('visualize:enableLabs'); return visualizationService - .findListItems(filter, config.get('savedObjects:listingLimit')) + .findListItems(filter, uiSettings.get('savedObjects:listingLimit')) .then(result => { this.totalItems = result.total; @@ -103,11 +113,11 @@ export function VisualizeListingController($injector, createNewVis) { this.deleteSelectedItems = function deleteSelectedItems(selectedItems) { return Promise.all( selectedItems.map(item => { - return savedObjectClient.delete(item.savedObjectType, item.id); + return savedObjectsClient.delete(item.savedObjectType, item.id); }) ) .then(() => { - chromeLegacy.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + legacyChrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); }) .catch(error => { toastNotifications.addError(error, { @@ -126,7 +136,7 @@ export function VisualizeListingController($injector, createNewVis) { }, ]); - this.listingLimit = config.get('savedObjects:listingLimit'); + this.listingLimit = uiSettings.get('savedObjects:listingLimit'); - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index efab03303aa809..890fa64af96935 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -27,22 +27,21 @@ import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elas import { getServices } from '../kibana_services'; -const { capabilities, toastNotifications, uiSettings } = getServices(); - class VisualizeListingTable extends Component { constructor(props) { super(props); } render() { + const { visualizeCapabilities, uiSettings, toastNotifications } = getServices(); return ( item.canDelete} diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts new file mode 100644 index 00000000000000..1aa2d70dabea6e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -0,0 +1,161 @@ +/* + * 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 { + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; + +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { DataPublicPluginStart } from '../../../../../plugins/data/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; +import { VisualizeConstants } from './visualize_constants'; +import { setServices, VisualizeKibanaServices } from './kibana_services'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../../plugins/home/public'; +import { defaultEditor, VisEditorTypesRegistryProvider } from './legacy_imports'; +import { SavedVisualizations } from './types'; + +export interface LegacyAngularInjectedDependencies { + legacyChrome: any; + editorTypes: any; + savedObjectRegistry: any; + savedVisualizations: SavedVisualizations; +} + +export interface VisualizePluginStartDependencies { + data: DataPublicPluginStart; + embeddables: IEmbeddableStart; + navigation: NavigationStart; + share: SharePluginStart; + visualizations: VisualizationsStart; +} + +export interface VisualizePluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + }; + home: HomePublicPluginSetup; + kibana_legacy: KibanaLegacySetup; +} + +export class VisualizePlugin implements Plugin { + private startDependencies: { + data: DataPublicPluginStart; + embeddables: IEmbeddableStart; + navigation: NavigationStart; + savedObjectsClient: SavedObjectsClientContract; + share: SharePluginStart; + visualizations: VisualizationsStart; + } | null = null; + + public async setup( + core: CoreSetup, + { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: VisualizePluginSetupDependencies + ) { + kibana_legacy.registerLegacyApp({ + id: 'visualize', + title: 'Visualize', + mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + + const { + savedObjectsClient, + embeddables, + navigation, + visualizations, + data, + share, + } = this.startDependencies; + + const angularDependencies = await getAngularDependencies(); + const deps: VisualizeKibanaServices = { + ...angularDependencies, + addBasePath: contextCore.http.basePath.prepend, + core: contextCore as LegacyCoreStart, + chrome: contextCore.chrome, + data, + embeddables, + getBasePath: core.http.basePath.get, + indexPatterns: data.indexPatterns, + localStorage: new Storage(localStorage), + navigation, + savedObjectsClient, + savedQueryService: data.query.savedQueries, + share, + toastNotifications: contextCore.notifications.toasts, + uiSettings: contextCore.uiSettings, + visualizeCapabilities: contextCore.application.capabilities.visualize, + visualizations, + }; + setServices(deps); + + const { renderApp } = await import('./application'); + return renderApp(params.element, params.appBasePath, deps); + }, + }); + + home.featureCatalogue.register({ + id: 'visualize', + title: 'Visualize', + description: i18n.translate('kbn.visualize.visualizeDescription', { + defaultMessage: + 'Create visualizations and aggregate data stores in your Elasticsearch indices.', + }), + icon: 'visualizeApp', + path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); + + VisEditorTypesRegistryProvider.register(defaultEditor); + } + + public start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { embeddables, navigation, data, share, visualizations }: VisualizePluginStartDependencies + ) { + this.startDependencies = { + data, + embeddables, + navigation, + savedObjectsClient, + share, + visualizations, + }; + + const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); + embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + } +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts index c83f7f5a5da8b0..b6a39812153846 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisSavedObject } from './kibana_services'; +import { VisSavedObject } from './legacy_imports'; export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts new file mode 100644 index 00000000000000..c64287a0e63b82 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IModule } from 'angular'; +import { VisualizeKibanaServices } from './kibana_services'; + +// @ts-ignore +import { initEditorDirective } from './editor/editor'; +// @ts-ignore +import { initListingDirective } from './listing/visualize_listing'; + +export function initVisualizeAppDirective(app: IModule, deps: VisualizeKibanaServices) { + initEditorDirective(app, deps); + initListingDirective(app); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 4aa614b68ea232..5be5f58994887c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -2,6 +2,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`] = ` @@ -1287,6 +1307,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` exports[`NewVisModal should render as expected 1`] = ` diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 99d9590e750fd0..0dd2091bbfee09 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -20,33 +20,17 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { NewVisModal } from './new_vis_modal'; -import { VisType } from '../kibana_services'; +import { VisType } from '../legacy_imports'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; -jest.mock('../kibana_services', () => { - const mock = { - addBasePath: jest.fn(path => `root${path}`), - uiSettings: { get: jest.fn() }, - createUiStatsReporter: () => jest.fn(), - }; - - return { - getServices: () => mock, - VisType: {}, - METRIC_TYPE: 'metricType', - }; -}); - -import { getServices } from '../kibana_services'; +jest.mock('../legacy_imports', () => ({ + State: () => null, + AppState: () => null, +})); -beforeEach(() => { - jest.clearAllMocks(); -}); +import { NewVisModal } from './new_vis_modal'; describe('NewVisModal', () => { - const settingsGet = getServices().uiSettings.get as jest.Mock; - const defaultVisTypeParams = { hidden: false, visualization: class Controller { @@ -76,17 +60,36 @@ describe('NewVisModal', () => { }, getAliases: () => [], }; + const addBasePath = (url: string) => `testbasepath${url}`; + const settingsGet = jest.fn(); + const uiSettings: any = { get: settingsGet }; + + beforeEach(() => { + jest.clearAllMocks(); + }); it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper).toMatchSnapshot(); }); it('should show a button for regular visualizations', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); }); @@ -95,7 +98,13 @@ describe('NewVisModal', () => { it('should open the editor for visualizations without search', () => { window.location.assign = jest.fn(); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); visButton.simulate('click'); @@ -110,6 +119,8 @@ describe('NewVisModal', () => { onClose={() => null} visTypesRegistry={visTypes} editorParams={['foo=true', 'bar=42']} + addBasePath={addBasePath} + uiSettings={uiSettings} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -121,7 +132,13 @@ describe('NewVisModal', () => { describe('filter for visualization types', () => { it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); searchBox.simulate('change', { target: { value: 'with' } }); @@ -133,7 +150,13 @@ describe('NewVisModal', () => { it('should not show experimental visualizations if visualize:enableLabs is false', () => { settingsGet.mockReturnValue(false); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); }); @@ -141,7 +164,13 @@ describe('NewVisModal', () => { it('should show experimental visualizations if visualize:enableLabs is true', () => { settingsGet.mockReturnValue(true); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 420f0e5198056c..0b46b562f2146a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,20 +22,21 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; +import { VisType } from '../legacy_imports'; import { VisualizeConstants } from '../visualize_constants'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices, METRIC_TYPE, VisType } from '../kibana_services'; - -const { addBasePath, createUiStatsReporter, uiSettings } = getServices(); - interface TypeSelectionProps { isOpen: boolean; onClose: () => void; visTypesRegistry: TypesStart; editorParams?: string[]; + addBasePath: (path: string) => string; + uiSettings: IUiSettingsClient; } interface TypeSelectionState { @@ -55,7 +56,7 @@ class NewVisModal extends React.Component ); @@ -124,7 +126,7 @@ class NewVisModal extends React.Component void; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index fa2ca6747bc408..92320f7bb443a0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -20,7 +20,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { IUiSettingsClient } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -30,7 +31,9 @@ interface ShowNewVisModalParams { export function showNewVisModal( visTypeRegistry: TypesStart, - { editorParams = [] }: ShowNewVisModalParams = {} + { editorParams = [] }: ShowNewVisModalParams = {}, + addBasePath: (path: string) => string, + uiSettings: IUiSettingsClient ) { const container = document.createElement('div'); const onClose = () => { @@ -40,14 +43,16 @@ export function showNewVisModal( document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx index 382f475669f5dc..3093499a030c8d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx @@ -21,18 +21,6 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; -jest.mock('../../kibana_services', () => { - return { - getServices: () => ({ - addBasePath: jest.fn((url: string) => `testbasepath${url}`), - }), - }; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - describe('NewVisHelp', () => { it('should render as expected', () => { expect( @@ -53,6 +41,7 @@ describe('NewVisHelp', () => { stage: 'production', }, ]} + addBasePath={(url: string) => `testbasepath${url}`} /> ) ).toMatchInlineSnapshot(` diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx index 44c36f7d17d550..107cbc0e754b51 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx @@ -22,10 +22,9 @@ import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; import { VisTypeAliasListEntry } from './type_selection'; -import { getServices } from '../../kibana_services'; - interface Props { promotedTypes: VisTypeAliasListEntry[]; + addBasePath: (path: string) => string; } export function NewVisHelp(props: Props) { @@ -43,7 +42,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

string; onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; visTypesRegistry: TypesStart; showExperimental: boolean; @@ -153,6 +154,7 @@ class TypeSelection extends React.Component t.promotion)} + addBasePath={this.props.addBasePath} /> )} diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js index 43d2962df0a1e0..9df866d29a8a28 100644 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ b/src/legacy/ui/public/vis/editors/default/default.js @@ -33,12 +33,11 @@ import { keyCodes } from '@elastic/eui'; import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; import { DefaultEditorSize } from '../../editor_size'; -import { VisEditorTypesRegistryProvider } from '../../../registry/vis_editor_types'; import { AggGroupNames } from './agg_groups'; import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; -const defaultEditor = function ($rootScope, $compile, getAppState) { +const defaultEditor = function ($rootScope, $compile) { return class DefaultEditor { static key = 'default'; @@ -58,7 +57,7 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { } } - render({ uiState, timeRange, filters, query }) { + render({ uiState, timeRange, filters, query, appState }) { let $scope; const updateScope = () => { @@ -161,7 +160,7 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(this.savedObj, { uiState: uiState, - appState: getAppState(), + appState, timeRange: timeRange, filters: filters || [], query: query, @@ -195,6 +194,4 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { }; }; -VisEditorTypesRegistryProvider.register(defaultEditor); - export { defaultEditor }; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 291d9feea3c402..80bee411757715 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -50,12 +50,6 @@ export const UI_EXPORT_DEFAULTS = { fieldFormatEditors: [ 'ui/field_editor/components/field_format_editor/register' ], - visEditorTypes: [ - 'ui/vis/editors/default/default', - ], - embeddableFactories: [ - 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory', - ], search: [ 'ui/courier/search_strategy/default_search_strategy', ], diff --git a/tasks/config/run.js b/tasks/config/run.js index e4071c8b7d0abc..97a0f381f2aa48 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -269,7 +269,6 @@ module.exports = function (grunt) { '--config', 'test/plugin_functional/config.js', '--bail', '--debug', - '--kibana-install-dir', KIBANA_INSTALL_DIR, ], }), diff --git a/x-pack/legacy/plugins/canvas/public/legacy_start.ts b/x-pack/legacy/plugins/canvas/public/legacy_start.ts index 49ec7acd6375dc..972427e166afcd 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy_start.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy_start.ts @@ -11,7 +11,6 @@ import 'ui/autoload/all'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/savedObjectTypes'; import 'uiExports/spyModes'; import 'uiExports/embeddableFactories'; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 8093c57d2631aa..8c6ddfebcb6cc6 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -18,7 +18,6 @@ import 'uiExports/contextMenuActions'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/inspectorViews'; import 'uiExports/interpreter'; import 'uiExports/savedObjectTypes'; From 1cdbd600a9dda425800830b951fc4a075e2c8dbd Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 10 Dec 2019 12:19:50 +0100 Subject: [PATCH 34/36] moves eui mapping to own file (#52518) --- src/core/public/i18n/i18n_eui_mapping.tsx | 574 +++++++++++++++++++++ src/core/public/i18n/i18n_service.tsx | 577 +--------------------- 2 files changed, 577 insertions(+), 574 deletions(-) create mode 100644 src/core/public/i18n/i18n_eui_mapping.tsx diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx new file mode 100644 index 00000000000000..4c7cdd18d03f62 --- /dev/null +++ b/src/core/public/i18n/i18n_eui_mapping.tsx @@ -0,0 +1,574 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface EuiValues { + [key: string]: any; +} + +export const euiContextMapping = { + 'euiBasicTable.selectAllRows': i18n.translate('core.euiBasicTable.selectAllRows', { + defaultMessage: 'Select all rows', + description: 'ARIA and displayed label on a checkbox to select all table rows', + }), + 'euiBasicTable.selectThisRow': i18n.translate('core.euiBasicTable.selectThisRow', { + defaultMessage: 'Select this row', + description: 'ARIA and displayed label on a checkbox to select a single table row', + }), + 'euiBasicTable.tableDescription': ({ itemCount }: EuiValues) => + i18n.translate('core.euiBasicTable.tableDescription', { + defaultMessage: 'Below is a table of {itemCount} items.', + values: { itemCount }, + description: 'Screen reader text to describe the size of a table', + }), + 'euiBottomBar.screenReaderAnnouncement': i18n.translate( + 'core.euiBottomBar.screenReaderAnnouncement', + { + defaultMessage: + 'There is a new menu opening with page level controls at the end of the document.', + description: + 'Screen reader announcement that functionality is available in the page document', + } + ), + 'euiBreadcrumbs.collapsedBadge.ariaLabel': i18n.translate( + 'core.euiBreadcrumbs.collapsedBadge.ariaLabel', + { + defaultMessage: 'Show all breadcrumbs', + description: 'Displayed when one or more breadcrumbs are hidden.', + } + ), + 'euiCardSelect.select': i18n.translate('core.euiCardSelect.select', { + defaultMessage: 'Select', + description: 'Displayed button text when a card option can be selected.', + }), + 'euiCardSelect.selected': i18n.translate('core.euiCardSelect.selected', { + defaultMessage: 'Selected', + description: 'Displayed button text when a card option is selected.', + }), + 'euiCardSelect.unavailable': i18n.translate('core.euiCardSelect.unavailable', { + defaultMessage: 'Unavailable', + description: 'Displayed button text when a card option is unavailable.', + }), + 'euiCodeBlock.copyButton': i18n.translate('core.euiCodeBlock.copyButton', { + defaultMessage: 'Copy', + description: 'ARIA label for a button that copies source code text to the clipboard', + }), + 'euiCodeEditor.startEditing': i18n.translate('core.euiCodeEditor.startEditing', { + defaultMessage: 'Press Enter to start editing.', + }), + 'euiCodeEditor.startInteracting': i18n.translate('core.euiCodeEditor.startInteracting', { + defaultMessage: 'Press Enter to start interacting with the code.', + }), + 'euiCodeEditor.stopEditing': i18n.translate('core.euiCodeEditor.stopEditing', { + defaultMessage: "When you're done, press Escape to stop editing.", + }), + 'euiCodeEditor.stopInteracting': i18n.translate('core.euiCodeEditor.stopInteracting', { + defaultMessage: "When you're done, press Escape to stop interacting with the code.", + }), + 'euiCollapsedItemActions.allActions': i18n.translate('core.euiCollapsedItemActions.allActions', { + defaultMessage: 'All actions', + description: 'ARIA label and tooltip content describing a button that expands an actions menu', + }), + 'euiColorPicker.screenReaderAnnouncement': i18n.translate( + 'core.euiColorPicker.screenReaderAnnouncement', + { + defaultMessage: + 'A popup with a range of selectable colors opened. Tab forward to cycle through colors choices or press escape to close this popup.', + description: + 'Message when the color picker popover is opened. Describes the interaction with the elements in the popover.', + } + ), + 'euiColorPicker.swatchAriaLabel': ({ swatch }: EuiValues) => + i18n.translate('core.euiColorPicker.swatchAriaLabel', { + defaultMessage: 'Select {swatch} as the color', + values: { swatch }, + description: + 'Screen reader text to describe the action and hex value of the selectable option', + }), + 'euiColorStopThumb.removeLabel': i18n.translate('core.euiColorStopThumb.removeLabel', { + defaultMessage: 'Remove this stop', + description: 'Label accompanying a button whose action will remove the color stop', + }), + 'euiColorStopThumb.screenReaderAnnouncement': i18n.translate( + 'core.euiColorStopThumb.screenReaderAnnouncement', + { + defaultMessage: + 'A popup with a color stop edit form opened. Tab forward to cycle through form controls or press escape to close this popup.', + description: + 'Message when the color picker popover has opened for an individual color stop thumb.', + } + ), + 'euiColorStops.screenReaderAnnouncement': ({ label, readOnly, disabled }: EuiValues) => + i18n.translate('core.euiColorStops.screenReaderAnnouncement', { + defaultMessage: + '{label}: {readOnly} {disabled} Color stop picker. Each stop consists of a number and corresponding color value. Use the Down and Up arrow keys to select individual stops. Press the Enter key to create a new stop.', + values: { label, readOnly, disabled }, + description: + 'Screen reader text to describe the composite behavior of the color stops component.', + }), + 'euiColumnSelector.hideAll': i18n.translate('core.euiColumnSelector.hideAll', { + defaultMessage: 'Hide all', + }), + 'euiColumnSelector.selectAll': i18n.translate('core.euiColumnSelector.selectAll', { + defaultMessage: 'Show all', + }), + 'euiColumnSorting.clearAll': i18n.translate('core.euiColumnSorting.clearAll', { + defaultMessage: 'Clear sorting', + }), + 'euiColumnSorting.emptySorting': i18n.translate('core.euiColumnSorting.emptySorting', { + defaultMessage: 'Currently no fields are sorted', + }), + 'euiColumnSorting.pickFields': i18n.translate('core.euiColumnSorting.pickFields', { + defaultMessage: 'Pick fields to sort by', + }), + 'euiColumnSorting.sortFieldAriaLabel': i18n.translate( + 'core.euiColumnSorting.sortFieldAriaLabel', + { + defaultMessage: 'Sort by:', + } + ), + 'euiColumnSortingDraggable.activeSortLabel': i18n.translate( + 'core.euiColumnSortingDraggable.activeSortLabel', + { + defaultMessage: 'is sorting this data grid', + } + ), + 'euiColumnSortingDraggable.defaultSortAsc': i18n.translate( + 'core.euiColumnSortingDraggable.defaultSortAsc', + { + defaultMessage: 'A-Z', + description: 'Ascending sort label', + } + ), + 'euiColumnSortingDraggable.defaultSortDesc': i18n.translate( + 'core.euiColumnSortingDraggable.defaultSortDesc', + { + defaultMessage: 'Z-A', + description: 'Descending sort label', + } + ), + 'euiColumnSortingDraggable.removeSortLabel': i18n.translate( + 'core.euiColumnSortingDraggable.removeSortLabel', + { + defaultMessage: 'Remove from data grid sort:', + } + ), + 'euiColumnSortingDraggable.toggleLegend': i18n.translate( + 'core.euiColumnSortingDraggable.toggleLegend', + { + defaultMessage: 'Select sorting method for field:', + } + ), + 'euiComboBoxOptionsList.allOptionsSelected': i18n.translate( + 'core.euiComboBoxOptionsList.allOptionsSelected', + { + defaultMessage: "You've selected all available options", + } + ), + 'euiComboBoxOptionsList.alreadyAdded': ({ label }: EuiValues) => ( + + ), + 'euiComboBoxOptionsList.createCustomOption': ({ key, searchValue }: EuiValues) => ( + + ), + 'euiComboBoxOptionsList.loadingOptions': i18n.translate( + 'core.euiComboBoxOptionsList.loadingOptions', + { + defaultMessage: 'Loading options', + description: 'Placeholder message while data is asynchronously loaded', + } + ), + 'euiComboBoxOptionsList.noAvailableOptions': i18n.translate( + 'core.euiComboBoxOptionsList.noAvailableOptions', + { + defaultMessage: "There aren't any options available", + } + ), + 'euiComboBoxOptionsList.noMatchingOptions': ({ searchValue }: EuiValues) => ( + + ), + 'euiComboBoxPill.removeSelection': ({ children }: EuiValues) => + i18n.translate('core.euiComboBoxPill.removeSelection', { + defaultMessage: 'Remove {children} from selection in this group', + values: { children }, + description: 'ARIA label, `children` is the human-friendly value of an option', + }), + 'euiCommonlyUsedTimeRanges.legend': i18n.translate('core.euiCommonlyUsedTimeRanges.legend', { + defaultMessage: 'Commonly used', + }), + 'euiDataGrid.screenReaderNotice': i18n.translate('core.euiDataGrid.screenReaderNotice', { + defaultMessage: 'Cell contains interactive content.', + }), + 'euiDataGridCell.expandButtonTitle': i18n.translate('core.euiDataGridCell.expandButtonTitle', { + defaultMessage: 'Click or hit enter to interact with cell content', + }), + 'euiDataGridSchema.booleanSortTextAsc': i18n.translate( + 'core.euiDataGridSchema.booleanSortTextAsc', + { + defaultMessage: 'True-False', + description: 'Ascending boolean label', + } + ), + 'euiDataGridSchema.booleanSortTextDesc': i18n.translate( + 'core.euiDataGridSchema.booleanSortTextDesc', + { + defaultMessage: 'False-True', + description: 'Descending boolean label', + } + ), + 'euiDataGridSchema.currencySortTextAsc': i18n.translate( + 'core.euiDataGridSchema.currencySortTextAsc', + { + defaultMessage: 'Low-High', + description: 'Ascending currency label', + } + ), + 'euiDataGridSchema.currencySortTextDesc': i18n.translate( + 'core.euiDataGridSchema.currencySortTextDesc', + { + defaultMessage: 'High-Low', + description: 'Descending currency label', + } + ), + 'euiDataGridSchema.dateSortTextAsc': i18n.translate('core.euiDataGridSchema.dateSortTextAsc', { + defaultMessage: 'New-Old', + description: 'Ascending date label', + }), + 'euiDataGridSchema.dateSortTextDesc': i18n.translate('core.euiDataGridSchema.dateSortTextDesc', { + defaultMessage: 'Old-New', + description: 'Descending date label', + }), + 'euiDataGridSchema.numberSortTextAsc': i18n.translate( + 'core.euiDataGridSchema.numberSortTextAsc', + { + defaultMessage: 'Low-High', + description: 'Ascending number label', + } + ), + 'euiDataGridSchema.numberSortTextDesc': i18n.translate( + 'core.euiDataGridSchema.numberSortTextDesc', + { + defaultMessage: 'High-Low', + description: 'Descending number label', + } + ), + 'euiDataGridSchema.jsonSortTextAsc': i18n.translate('core.euiDataGridSchema.jsonSortTextAsc', { + defaultMessage: 'Small-Large', + description: 'Ascending size label', + }), + 'euiDataGridSchema.jsonSortTextDesc': i18n.translate('core.euiDataGridSchema.jsonSortTextDesc', { + defaultMessage: 'Large-Small', + description: 'Descending size label', + }), + 'euiFilterButton.filterBadge': ({ count, hasActiveFilters }: EuiValues) => + i18n.translate('core.euiFilterButton.filterBadge', { + defaultMessage: '${count} ${filterCountLabel} filters', + values: { count, filterCountLabel: hasActiveFilters ? 'active' : 'available' }, + }), + 'euiForm.addressFormErrors': i18n.translate('core.euiForm.addressFormErrors', { + defaultMessage: 'Please address the errors in your form.', + }), + 'euiFormControlLayoutClearButton.label': i18n.translate( + 'core.euiFormControlLayoutClearButton.label', + { + defaultMessage: 'Clear input', + description: 'ARIA label on a button that removes any entry in a form field', + } + ), + 'euiHeaderAlert.dismiss': i18n.translate('core.euiHeaderAlert.dismiss', { + defaultMessage: 'Dismiss', + description: 'ARIA label on a button that dismisses/removes a notification', + }), + 'euiHeaderLinks.appNavigation': i18n.translate('core.euiHeaderLinks.appNavigation', { + defaultMessage: 'App navigation', + description: 'ARIA label on a `nav` element', + }), + 'euiHeaderLinks.openNavigationMenu': i18n.translate('core.euiHeaderLinks.openNavigationMenu', { + defaultMessage: 'Open navigation menu', + }), + 'euiHue.label': i18n.translate('core.euiHue.label', { + defaultMessage: 'Select the HSV color mode "hue" value', + }), + 'euiImage.closeImage': ({ alt }: EuiValues) => + i18n.translate('core.euiImage.closeImage', { + defaultMessage: 'Close full screen {alt} image', + values: { alt }, + }), + 'euiImage.openImage': ({ alt }: EuiValues) => + i18n.translate('core.euiImage.openImage', { + defaultMessage: 'Open full screen {alt} image', + values: { alt }, + }), + 'euiLink.external.ariaLabel': i18n.translate('core.euiLink.external.ariaLabel', { + defaultMessage: 'External link', + }), + 'euiModal.closeModal': i18n.translate('core.euiModal.closeModal', { + defaultMessage: 'Closes this modal window', + }), + 'euiPagination.jumpToLastPage': ({ pageCount }: EuiValues) => + i18n.translate('core.euiPagination.jumpToLastPage', { + defaultMessage: 'Jump to the last page, number {pageCount}', + values: { pageCount }, + }), + 'euiPagination.nextPage': i18n.translate('core.euiPagination.nextPage', { + defaultMessage: 'Next page', + }), + 'euiPagination.pageOfTotal': ({ page, total }: EuiValues) => + i18n.translate('core.euiPagination.pageOfTotal', { + defaultMessage: 'Page {page} of {total}', + values: { page, total }, + }), + 'euiPagination.previousPage': i18n.translate('core.euiPagination.previousPage', { + defaultMessage: 'Previous page', + }), + 'euiPopover.screenReaderAnnouncement': i18n.translate( + 'core.euiPopover.screenReaderAnnouncement', + { + defaultMessage: 'You are in a dialog. To close this dialog, hit escape.', + } + ), + 'euiQuickSelect.applyButton': i18n.translate('core.euiQuickSelect.applyButton', { + defaultMessage: 'Apply', + }), + 'euiQuickSelect.fullDescription': ({ timeTense, timeValue, timeUnit }: EuiValues) => + i18n.translate('core.euiQuickSelect.fullDescription', { + defaultMessage: 'Currently set to {timeTense} {timeValue} {timeUnit}.', + values: { timeTense, timeValue, timeUnit }, + }), + 'euiQuickSelect.legendText': i18n.translate('core.euiQuickSelect.legendText', { + defaultMessage: 'Quick select a time range', + }), + 'euiQuickSelect.nextLabel': i18n.translate('core.euiQuickSelect.nextLabel', { + defaultMessage: 'Next time window', + }), + 'euiQuickSelect.previousLabel': i18n.translate('core.euiQuickSelect.previousLabel', { + defaultMessage: 'Previous time window', + }), + 'euiQuickSelect.quickSelectTitle': i18n.translate('core.euiQuickSelect.quickSelectTitle', { + defaultMessage: 'Quick select', + }), + 'euiQuickSelect.tenseLabel': i18n.translate('core.euiQuickSelect.tenseLabel', { + defaultMessage: 'Time tense', + }), + 'euiQuickSelect.unitLabel': i18n.translate('core.euiQuickSelect.unitLabel', { + defaultMessage: 'Time unit', + }), + 'euiQuickSelect.valueLabel': i18n.translate('core.euiQuickSelect.valueLabel', { + defaultMessage: 'Time value', + }), + 'euiRefreshInterval.fullDescription': ({ optionValue, optionText }: EuiValues) => + i18n.translate('core.euiRefreshInterval.fullDescription', { + defaultMessage: 'Currently set to {optionValue} {optionText}.', + values: { optionValue, optionText }, + }), + 'euiRefreshInterval.legend': i18n.translate('core.euiRefreshInterval.legend', { + defaultMessage: 'Refresh every', + }), + 'euiRefreshInterval.start': i18n.translate('core.euiRefreshInterval.start', { + defaultMessage: 'Start', + }), + 'euiRefreshInterval.stop': i18n.translate('core.euiRefreshInterval.stop', { + defaultMessage: 'Stop', + }), + 'euiRelativeTab.fullDescription': ({ unit }: EuiValues) => + i18n.translate('core.euiRelativeTab.fullDescription', { + defaultMessage: 'The unit is changeable. Currently set to {unit}.', + values: { unit }, + }), + 'euiRelativeTab.relativeDate': ({ position }: EuiValues) => + i18n.translate('core.euiRelativeTab.relativeDate', { + defaultMessage: '{position} date', + values: { position }, + }), + 'euiRelativeTab.roundingLabel': ({ unit }: EuiValues) => + i18n.translate('core.euiRelativeTab.roundingLabel', { + defaultMessage: 'Round to the {unit}', + values: { unit }, + }), + 'euiRelativeTab.unitInputLabel': i18n.translate('core.euiRelativeTab.unitInputLabel', { + defaultMessage: 'Relative time span', + }), + 'euiSaturation.roleDescription': i18n.translate('core.euiSaturation.roleDescription', { + defaultMessage: 'HSV color mode saturation and value selection', + }), + 'euiSaturation.screenReaderAnnouncement': i18n.translate( + 'core.euiSaturation.screenReaderAnnouncement', + { + defaultMessage: + 'Use the arrow keys to navigate the square color gradient. The coordinates resulting from each key press will be used to calculate HSV color mode "saturation" and "value" numbers, in the range of 0 to 1. Left and right decrease and increase (respectively) the "saturation" value. Up and down decrease and increase (respectively) the "value" value.', + } + ), + 'euiSelectable.loadingOptions': i18n.translate('core.euiSelectable.loadingOptions', { + defaultMessage: 'Loading options', + description: 'Placeholder message while data is asynchronously loaded', + }), + 'euiSelectable.noAvailableOptions': i18n.translate('core.euiSelectable.noAvailableOptions', { + defaultMessage: "There aren't any options available", + }), + 'euiSelectable.noMatchingOptions': ({ searchValue }: EuiValues) => ( + + ), + 'euiStat.loadingText': i18n.translate('core.euiStat.loadingText', { + defaultMessage: 'Statistic is loading', + }), + 'euiStep.ariaLabel': ({ status }: EuiValues) => + i18n.translate('core.euiStep.ariaLabel', { + defaultMessage: '{stepStatus}', + values: { stepStatus: status === 'incomplete' ? 'Incomplete Step' : 'Step' }, + }), + 'euiStepHorizontal.buttonTitle': ({ step, title, disabled, isComplete }: EuiValues) => { + return i18n.translate('core.euiStepHorizontal.buttonTitle', { + defaultMessage: 'Step {step}: {title}{titleAppendix}', + values: { + step, + title, + titleAppendix: disabled ? ' is disabled' : isComplete ? ' is complete' : '', + }, + }); + }, + 'euiStepHorizontal.step': i18n.translate('core.euiStepHorizontal.step', { + defaultMessage: 'Step', + description: 'Screen reader text announcing information about a step in some process', + }), + 'euiStepNumber.hasErrors': i18n.translate('core.euiStepNumber.hasErrors', { + defaultMessage: 'has errors', + description: + 'Used as the title attribute on an image or svg icon to indicate a given process step has errors', + }), + 'euiStepNumber.hasWarnings': i18n.translate('core.euiStepNumber.hasWarnings', { + defaultMessage: 'has warnings', + description: + 'Used as the title attribute on an image or svg icon to indicate a given process step has warnings', + }), + 'euiStepNumber.isComplete': i18n.translate('core.euiStepNumber.isComplete', { + defaultMessage: 'complete', + description: + 'Used as the title attribute on an image or svg icon to indicate a given process step is complete', + }), + 'euiStyleSelector.buttonText': i18n.translate('core.euiStyleSelector.buttonText', { + defaultMessage: 'Density', + }), + 'euiSuperDatePicker.showDatesButtonLabel': i18n.translate( + 'core.euiSuperDatePicker.showDatesButtonLabel', + { + defaultMessage: 'Show dates', + description: 'Displayed in a button that shows date picker', + } + ), + 'euiSuperSelect.screenReaderAnnouncement': ({ optionsCount }: EuiValues) => + i18n.translate('core.euiSuperSelect.screenReaderAnnouncement', { + defaultMessage: + 'You are in a form selector of {optionsCount} items and must select a single option. Use the Up and Down keys to navigate or Escape to close.', + values: { optionsCount }, + }), + 'euiSuperSelectControl.selectAnOption': ({ selectedValue }: EuiValues) => + i18n.translate('core.euiSuperSelectControl.selectAnOption', { + defaultMessage: 'Select an option: {selectedValue}, is selected', + values: { selectedValue }, + }), + 'euiSuperUpdateButton.cannotUpdateTooltip': i18n.translate( + 'core.euiSuperUpdateButton.cannotUpdateTooltip', + { + defaultMessage: 'Cannot update', + description: "Displayed in a tooltip when updates can't happen", + } + ), + 'euiSuperUpdateButton.clickToApplyTooltip': i18n.translate( + 'core.euiSuperUpdateButton.clickToApplyTooltip', + { + defaultMessage: 'Click to apply', + description: "Displayed in a tooltip when there are changes that haven't been applied", + } + ), + 'euiSuperUpdateButton.refreshButtonLabel': i18n.translate( + 'core.euiSuperUpdateButton.refreshButtonLabel', + { + defaultMessage: 'Refresh', + description: 'Displayed in a button that refreshes based on date picked', + } + ), + 'euiSuperUpdateButton.updatingButtonLabel': i18n.translate( + 'core.euiSuperUpdateButton.updatingButtonLabel', + { + defaultMessage: 'Updating', + description: 'Displayed in a button that refreshes when updates are happening', + } + ), + 'euiSuperUpdateButton.updateButtonLabel': i18n.translate( + 'core.euiSuperUpdateButton.updateButtonLabel', + { + defaultMessage: 'Update', + description: 'Displayed in a button that updates based on date picked', + } + ), + 'euiTablePagination.rowsPerPage': i18n.translate('core.euiTablePagination.rowsPerPage', { + defaultMessage: 'Rows per page', + description: 'Displayed in a button that toggles a table pagination menu', + }), + 'euiTablePagination.rowsPerPageOption': ({ rowsPerPage }: EuiValues) => + i18n.translate('core.euiTablePagination.rowsPerPageOption', { + defaultMessage: '{rowsPerPage} rows', + description: 'Displayed in a button that toggles the number of visible rows', + values: { rowsPerPage }, + }), + 'euiTableSortMobile.sorting': i18n.translate('core.euiTableSortMobile.sorting', { + defaultMessage: 'Sorting', + description: 'Displayed in a button that toggles a table sorting menu', + }), + 'euiToast.dismissToast': i18n.translate('core.euiToast.dismissToast', { + defaultMessage: 'Dismiss toast', + }), + 'euiToast.newNotification': i18n.translate('core.euiToast.newNotification', { + defaultMessage: 'A new notification appears', + }), + 'euiToast.notification': i18n.translate('core.euiToast.notification', { + defaultMessage: 'Notification', + description: 'ARIA label on an element containing a notification', + }), + 'euiTreeView.ariaLabel': ({ nodeLabel, ariaLabel }: EuiValues) => + i18n.translate('core.euiTreeView.ariaLabel', { + defaultMessage: '{nodeLabel} child of {ariaLabel}', + values: { nodeLabel, ariaLabel }, + }), + 'euiTreeView.listNavigationInstructions': i18n.translate( + 'core.euiTreeView.listNavigationInstructions', + { + defaultMessage: 'You can quickly navigate this list using arrow keys.', + } + ), +}; diff --git a/src/core/public/i18n/i18n_service.tsx b/src/core/public/i18n/i18n_service.tsx index 721c5d49634f4b..501d83e36b1b94 100644 --- a/src/core/public/i18n/i18n_service.tsx +++ b/src/core/public/i18n/i18n_service.tsx @@ -18,14 +18,10 @@ */ import React from 'react'; - import { EuiContext } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { I18nProvider } from '@kbn/i18n/react'; -interface EuiValues { - [key: string]: any; -} +import { euiContextMapping } from './i18n_eui_mapping'; /** * Service that is responsible for i18n capabilities. @@ -42,575 +38,8 @@ export class I18nService { */ public getContext(): I18nStart { const mapping = { - 'euiBasicTable.selectAllRows': i18n.translate('core.euiBasicTable.selectAllRows', { - defaultMessage: 'Select all rows', - description: 'ARIA and displayed label on a checkbox to select all table rows', - }), - 'euiBasicTable.selectThisRow': i18n.translate('core.euiBasicTable.selectThisRow', { - defaultMessage: 'Select this row', - description: 'ARIA and displayed label on a checkbox to select a single table row', - }), - 'euiBasicTable.tableDescription': ({ itemCount }: EuiValues) => - i18n.translate('core.euiBasicTable.tableDescription', { - defaultMessage: 'Below is a table of {itemCount} items.', - values: { itemCount }, - description: 'Screen reader text to describe the size of a table', - }), - 'euiBottomBar.screenReaderAnnouncement': i18n.translate( - 'core.euiBottomBar.screenReaderAnnouncement', - { - defaultMessage: - 'There is a new menu opening with page level controls at the end of the document.', - description: - 'Screen reader announcement that functionality is available in the page document', - } - ), - 'euiBreadcrumbs.collapsedBadge.ariaLabel': i18n.translate( - 'core.euiBreadcrumbs.collapsedBadge.ariaLabel', - { - defaultMessage: 'Show all breadcrumbs', - description: 'Displayed when one or more breadcrumbs are hidden.', - } - ), - 'euiCardSelect.select': i18n.translate('core.euiCardSelect.select', { - defaultMessage: 'Select', - description: 'Displayed button text when a card option can be selected.', - }), - 'euiCardSelect.selected': i18n.translate('core.euiCardSelect.selected', { - defaultMessage: 'Selected', - description: 'Displayed button text when a card option is selected.', - }), - 'euiCardSelect.unavailable': i18n.translate('core.euiCardSelect.unavailable', { - defaultMessage: 'Unavailable', - description: 'Displayed button text when a card option is unavailable.', - }), - 'euiCodeBlock.copyButton': i18n.translate('core.euiCodeBlock.copyButton', { - defaultMessage: 'Copy', - description: 'ARIA label for a button that copies source code text to the clipboard', - }), - 'euiCodeEditor.startEditing': i18n.translate('core.euiCodeEditor.startEditing', { - defaultMessage: 'Press Enter to start editing.', - }), - 'euiCodeEditor.startInteracting': i18n.translate('core.euiCodeEditor.startInteracting', { - defaultMessage: 'Press Enter to start interacting with the code.', - }), - 'euiCodeEditor.stopEditing': i18n.translate('core.euiCodeEditor.stopEditing', { - defaultMessage: "When you're done, press Escape to stop editing.", - }), - 'euiCodeEditor.stopInteracting': i18n.translate('core.euiCodeEditor.stopInteracting', { - defaultMessage: "When you're done, press Escape to stop interacting with the code.", - }), - 'euiCollapsedItemActions.allActions': i18n.translate( - 'core.euiCollapsedItemActions.allActions', - { - defaultMessage: 'All actions', - description: - 'ARIA label and tooltip content describing a button that expands an actions menu', - } - ), - 'euiColorPicker.screenReaderAnnouncement': i18n.translate( - 'core.euiColorPicker.screenReaderAnnouncement', - { - defaultMessage: - 'A popup with a range of selectable colors opened. Tab forward to cycle through colors choices or press escape to close this popup.', - description: - 'Message when the color picker popover is opened. Describes the interaction with the elements in the popover.', - } - ), - 'euiColorPicker.swatchAriaLabel': ({ swatch }: EuiValues) => - i18n.translate('core.euiColorPicker.swatchAriaLabel', { - defaultMessage: 'Select {swatch} as the color', - values: { swatch }, - description: - 'Screen reader text to describe the action and hex value of the selectable option', - }), - 'euiColorStopThumb.removeLabel': i18n.translate('core.euiColorStopThumb.removeLabel', { - defaultMessage: 'Remove this stop', - description: 'Label accompanying a button whose action will remove the color stop', - }), - 'euiColorStopThumb.screenReaderAnnouncement': i18n.translate( - 'core.euiColorStopThumb.screenReaderAnnouncement', - { - defaultMessage: - 'A popup with a color stop edit form opened. Tab forward to cycle through form controls or press escape to close this popup.', - description: - 'Message when the color picker popover has opened for an individual color stop thumb.', - } - ), - 'euiColorStops.screenReaderAnnouncement': ({ label, readOnly, disabled }: EuiValues) => - i18n.translate('core.euiColorStops.screenReaderAnnouncement', { - defaultMessage: - '{label}: {readOnly} {disabled} Color stop picker. Each stop consists of a number and corresponding color value. Use the Down and Up arrow keys to select individual stops. Press the Enter key to create a new stop.', - values: { label, readOnly, disabled }, - description: - 'Screen reader text to describe the composite behavior of the color stops component.', - }), - 'euiColumnSelector.hideAll': i18n.translate('core.euiColumnSelector.hideAll', { - defaultMessage: 'Hide all', - }), - 'euiColumnSelector.selectAll': i18n.translate('core.euiColumnSelector.selectAll', { - defaultMessage: 'Show all', - }), - 'euiColumnSorting.clearAll': i18n.translate('core.euiColumnSorting.clearAll', { - defaultMessage: 'Clear sorting', - }), - 'euiColumnSorting.emptySorting': i18n.translate('core.euiColumnSorting.emptySorting', { - defaultMessage: 'Currently no fields are sorted', - }), - 'euiColumnSorting.pickFields': i18n.translate('core.euiColumnSorting.pickFields', { - defaultMessage: 'Pick fields to sort by', - }), - 'euiColumnSorting.sortFieldAriaLabel': i18n.translate( - 'core.euiColumnSorting.sortFieldAriaLabel', - { - defaultMessage: 'Sort by:', - } - ), - 'euiColumnSortingDraggable.activeSortLabel': i18n.translate( - 'core.euiColumnSortingDraggable.activeSortLabel', - { - defaultMessage: 'is sorting this data grid', - } - ), - 'euiColumnSortingDraggable.defaultSortAsc': i18n.translate( - 'core.euiColumnSortingDraggable.defaultSortAsc', - { - defaultMessage: 'A-Z', - description: 'Ascending sort label', - } - ), - 'euiColumnSortingDraggable.defaultSortDesc': i18n.translate( - 'core.euiColumnSortingDraggable.defaultSortDesc', - { - defaultMessage: 'Z-A', - description: 'Descending sort label', - } - ), - 'euiColumnSortingDraggable.removeSortLabel': i18n.translate( - 'core.euiColumnSortingDraggable.removeSortLabel', - { - defaultMessage: 'Remove from data grid sort:', - } - ), - 'euiColumnSortingDraggable.toggleLegend': i18n.translate( - 'core.euiColumnSortingDraggable.toggleLegend', - { - defaultMessage: 'Select sorting method for field:', - } - ), - 'euiComboBoxOptionsList.allOptionsSelected': i18n.translate( - 'core.euiComboBoxOptionsList.allOptionsSelected', - { - defaultMessage: "You've selected all available options", - } - ), - 'euiComboBoxOptionsList.alreadyAdded': ({ label }: EuiValues) => ( - - ), - 'euiComboBoxOptionsList.createCustomOption': ({ key, searchValue }: EuiValues) => ( - - ), - 'euiComboBoxOptionsList.loadingOptions': i18n.translate( - 'core.euiComboBoxOptionsList.loadingOptions', - { - defaultMessage: 'Loading options', - description: 'Placeholder message while data is asynchronously loaded', - } - ), - 'euiComboBoxOptionsList.noAvailableOptions': i18n.translate( - 'core.euiComboBoxOptionsList.noAvailableOptions', - { - defaultMessage: "There aren't any options available", - } - ), - 'euiComboBoxOptionsList.noMatchingOptions': ({ searchValue }: EuiValues) => ( - - ), - 'euiComboBoxPill.removeSelection': ({ children }: EuiValues) => - i18n.translate('core.euiComboBoxPill.removeSelection', { - defaultMessage: 'Remove {children} from selection in this group', - values: { children }, - description: 'ARIA label, `children` is the human-friendly value of an option', - }), - 'euiCommonlyUsedTimeRanges.legend': i18n.translate('core.euiCommonlyUsedTimeRanges.legend', { - defaultMessage: 'Commonly used', - }), - 'euiDataGrid.screenReaderNotice': i18n.translate('core.euiDataGrid.screenReaderNotice', { - defaultMessage: 'Cell contains interactive content.', - }), - 'euiDataGridCell.expandButtonTitle': i18n.translate( - 'core.euiDataGridCell.expandButtonTitle', - { - defaultMessage: 'Click or hit enter to interact with cell content', - } - ), - 'euiDataGridSchema.booleanSortTextAsc': i18n.translate( - 'core.euiDataGridSchema.booleanSortTextAsc', - { - defaultMessage: 'True-False', - description: 'Ascending boolean label', - } - ), - 'euiDataGridSchema.booleanSortTextDesc': i18n.translate( - 'core.euiDataGridSchema.booleanSortTextDesc', - { - defaultMessage: 'False-True', - description: 'Descending boolean label', - } - ), - 'euiDataGridSchema.currencySortTextAsc': i18n.translate( - 'core.euiDataGridSchema.currencySortTextAsc', - { - defaultMessage: 'Low-High', - description: 'Ascending currency label', - } - ), - 'euiDataGridSchema.currencySortTextDesc': i18n.translate( - 'core.euiDataGridSchema.currencySortTextDesc', - { - defaultMessage: 'High-Low', - description: 'Descending currency label', - } - ), - 'euiDataGridSchema.dateSortTextAsc': i18n.translate( - 'core.euiDataGridSchema.dateSortTextAsc', - { - defaultMessage: 'New-Old', - description: 'Ascending date label', - } - ), - 'euiDataGridSchema.dateSortTextDesc': i18n.translate( - 'core.euiDataGridSchema.dateSortTextDesc', - { - defaultMessage: 'Old-New', - description: 'Descending date label', - } - ), - 'euiDataGridSchema.numberSortTextAsc': i18n.translate( - 'core.euiDataGridSchema.numberSortTextAsc', - { - defaultMessage: 'Low-High', - description: 'Ascending number label', - } - ), - 'euiDataGridSchema.numberSortTextDesc': i18n.translate( - 'core.euiDataGridSchema.numberSortTextDesc', - { - defaultMessage: 'High-Low', - description: 'Descending number label', - } - ), - 'euiDataGridSchema.jsonSortTextAsc': i18n.translate( - 'core.euiDataGridSchema.jsonSortTextAsc', - { - defaultMessage: 'Small-Large', - description: 'Ascending size label', - } - ), - 'euiDataGridSchema.jsonSortTextDesc': i18n.translate( - 'core.euiDataGridSchema.jsonSortTextDesc', - { - defaultMessage: 'Large-Small', - description: 'Descending size label', - } - ), - 'euiFilterButton.filterBadge': ({ count, hasActiveFilters }: EuiValues) => - i18n.translate('core.euiFilterButton.filterBadge', { - defaultMessage: '${count} ${filterCountLabel} filters', - values: { count, filterCountLabel: hasActiveFilters ? 'active' : 'available' }, - }), - 'euiForm.addressFormErrors': i18n.translate('core.euiForm.addressFormErrors', { - defaultMessage: 'Please address the errors in your form.', - }), - 'euiFormControlLayoutClearButton.label': i18n.translate( - 'core.euiFormControlLayoutClearButton.label', - { - defaultMessage: 'Clear input', - description: 'ARIA label on a button that removes any entry in a form field', - } - ), - 'euiHeaderAlert.dismiss': i18n.translate('core.euiHeaderAlert.dismiss', { - defaultMessage: 'Dismiss', - description: 'ARIA label on a button that dismisses/removes a notification', - }), - 'euiHeaderLinks.appNavigation': i18n.translate('core.euiHeaderLinks.appNavigation', { - defaultMessage: 'App navigation', - description: 'ARIA label on a `nav` element', - }), - 'euiHeaderLinks.openNavigationMenu': i18n.translate( - 'core.euiHeaderLinks.openNavigationMenu', - { - defaultMessage: 'Open navigation menu', - } - ), - 'euiHue.label': i18n.translate('core.euiHue.label', { - defaultMessage: 'Select the HSV color mode "hue" value', - }), - 'euiImage.closeImage': ({ alt }: EuiValues) => - i18n.translate('core.euiImage.closeImage', { - defaultMessage: 'Close full screen {alt} image', - values: { alt }, - }), - 'euiImage.openImage': ({ alt }: EuiValues) => - i18n.translate('core.euiImage.openImage', { - defaultMessage: 'Open full screen {alt} image', - values: { alt }, - }), - 'euiLink.external.ariaLabel': i18n.translate('core.euiLink.external.ariaLabel', { - defaultMessage: 'External link', - }), - 'euiModal.closeModal': i18n.translate('core.euiModal.closeModal', { - defaultMessage: 'Closes this modal window', - }), - 'euiPagination.jumpToLastPage': ({ pageCount }: EuiValues) => - i18n.translate('core.euiPagination.jumpToLastPage', { - defaultMessage: 'Jump to the last page, number {pageCount}', - values: { pageCount }, - }), - 'euiPagination.nextPage': i18n.translate('core.euiPagination.nextPage', { - defaultMessage: 'Next page', - }), - 'euiPagination.pageOfTotal': ({ page, total }: EuiValues) => - i18n.translate('core.euiPagination.pageOfTotal', { - defaultMessage: 'Page {page} of {total}', - values: { page, total }, - }), - 'euiPagination.previousPage': i18n.translate('core.euiPagination.previousPage', { - defaultMessage: 'Previous page', - }), - 'euiPopover.screenReaderAnnouncement': i18n.translate( - 'core.euiPopover.screenReaderAnnouncement', - { - defaultMessage: 'You are in a dialog. To close this dialog, hit escape.', - } - ), - 'euiQuickSelect.applyButton': i18n.translate('core.euiQuickSelect.applyButton', { - defaultMessage: 'Apply', - }), - 'euiQuickSelect.fullDescription': ({ timeTense, timeValue, timeUnit }: EuiValues) => - i18n.translate('core.euiQuickSelect.fullDescription', { - defaultMessage: 'Currently set to {timeTense} {timeValue} {timeUnit}.', - values: { timeTense, timeValue, timeUnit }, - }), - 'euiQuickSelect.legendText': i18n.translate('core.euiQuickSelect.legendText', { - defaultMessage: 'Quick select a time range', - }), - 'euiQuickSelect.nextLabel': i18n.translate('core.euiQuickSelect.nextLabel', { - defaultMessage: 'Next time window', - }), - 'euiQuickSelect.previousLabel': i18n.translate('core.euiQuickSelect.previousLabel', { - defaultMessage: 'Previous time window', - }), - 'euiQuickSelect.quickSelectTitle': i18n.translate('core.euiQuickSelect.quickSelectTitle', { - defaultMessage: 'Quick select', - }), - 'euiQuickSelect.tenseLabel': i18n.translate('core.euiQuickSelect.tenseLabel', { - defaultMessage: 'Time tense', - }), - 'euiQuickSelect.unitLabel': i18n.translate('core.euiQuickSelect.unitLabel', { - defaultMessage: 'Time unit', - }), - 'euiQuickSelect.valueLabel': i18n.translate('core.euiQuickSelect.valueLabel', { - defaultMessage: 'Time value', - }), - 'euiRefreshInterval.fullDescription': ({ optionValue, optionText }: EuiValues) => - i18n.translate('core.euiRefreshInterval.fullDescription', { - defaultMessage: 'Currently set to {optionValue} {optionText}.', - values: { optionValue, optionText }, - }), - 'euiRefreshInterval.legend': i18n.translate('core.euiRefreshInterval.legend', { - defaultMessage: 'Refresh every', - }), - 'euiRefreshInterval.start': i18n.translate('core.euiRefreshInterval.start', { - defaultMessage: 'Start', - }), - 'euiRefreshInterval.stop': i18n.translate('core.euiRefreshInterval.stop', { - defaultMessage: 'Stop', - }), - 'euiRelativeTab.fullDescription': ({ unit }: EuiValues) => - i18n.translate('core.euiRelativeTab.fullDescription', { - defaultMessage: 'The unit is changeable. Currently set to {unit}.', - values: { unit }, - }), - 'euiRelativeTab.relativeDate': ({ position }: EuiValues) => - i18n.translate('core.euiRelativeTab.relativeDate', { - defaultMessage: '{position} date', - values: { position }, - }), - 'euiRelativeTab.roundingLabel': ({ unit }: EuiValues) => - i18n.translate('core.euiRelativeTab.roundingLabel', { - defaultMessage: 'Round to the {unit}', - values: { unit }, - }), - 'euiRelativeTab.unitInputLabel': i18n.translate('core.euiRelativeTab.unitInputLabel', { - defaultMessage: 'Relative time span', - }), - 'euiSaturation.roleDescription': i18n.translate('core.euiSaturation.roleDescription', { - defaultMessage: 'HSV color mode saturation and value selection', - }), - 'euiSaturation.screenReaderAnnouncement': i18n.translate( - 'core.euiSaturation.screenReaderAnnouncement', - { - defaultMessage: - 'Use the arrow keys to navigate the square color gradient. The coordinates resulting from each key press will be used to calculate HSV color mode "saturation" and "value" numbers, in the range of 0 to 1. Left and right decrease and increase (respectively) the "saturation" value. Up and down decrease and increase (respectively) the "value" value.', - } - ), - 'euiSelectable.loadingOptions': i18n.translate('core.euiSelectable.loadingOptions', { - defaultMessage: 'Loading options', - description: 'Placeholder message while data is asynchronously loaded', - }), - 'euiSelectable.noAvailableOptions': i18n.translate('core.euiSelectable.noAvailableOptions', { - defaultMessage: "There aren't any options available", - }), - 'euiSelectable.noMatchingOptions': ({ searchValue }: EuiValues) => ( - - ), - 'euiStat.loadingText': i18n.translate('core.euiStat.loadingText', { - defaultMessage: 'Statistic is loading', - }), - 'euiStep.ariaLabel': ({ status }: EuiValues) => - i18n.translate('core.euiStep.ariaLabel', { - defaultMessage: '{stepStatus}', - values: { stepStatus: status === 'incomplete' ? 'Incomplete Step' : 'Step' }, - }), - 'euiStepHorizontal.buttonTitle': ({ step, title, disabled, isComplete }: EuiValues) => { - return i18n.translate('core.euiStepHorizontal.buttonTitle', { - defaultMessage: 'Step {step}: {title}{titleAppendix}', - values: { - step, - title, - titleAppendix: disabled ? ' is disabled' : isComplete ? ' is complete' : '', - }, - }); - }, - 'euiStepHorizontal.step': i18n.translate('core.euiStepHorizontal.step', { - defaultMessage: 'Step', - description: 'Screen reader text announcing information about a step in some process', - }), - 'euiStepNumber.hasErrors': i18n.translate('core.euiStepNumber.hasErrors', { - defaultMessage: 'has errors', - description: - 'Used as the title attribute on an image or svg icon to indicate a given process step has errors', - }), - 'euiStepNumber.hasWarnings': i18n.translate('core.euiStepNumber.hasWarnings', { - defaultMessage: 'has warnings', - description: - 'Used as the title attribute on an image or svg icon to indicate a given process step has warnings', - }), - 'euiStepNumber.isComplete': i18n.translate('core.euiStepNumber.isComplete', { - defaultMessage: 'complete', - description: - 'Used as the title attribute on an image or svg icon to indicate a given process step is complete', - }), - 'euiStyleSelector.buttonText': i18n.translate('core.euiStyleSelector.buttonText', { - defaultMessage: 'Density', - }), - 'euiSuperDatePicker.showDatesButtonLabel': i18n.translate( - 'core.euiSuperDatePicker.showDatesButtonLabel', - { - defaultMessage: 'Show dates', - description: 'Displayed in a button that shows date picker', - } - ), - 'euiSuperSelect.screenReaderAnnouncement': ({ optionsCount }: EuiValues) => - i18n.translate('core.euiSuperSelect.screenReaderAnnouncement', { - defaultMessage: - 'You are in a form selector of {optionsCount} items and must select a single option. Use the Up and Down keys to navigate or Escape to close.', - values: { optionsCount }, - }), - 'euiSuperSelectControl.selectAnOption': ({ selectedValue }: EuiValues) => - i18n.translate('core.euiSuperSelectControl.selectAnOption', { - defaultMessage: 'Select an option: {selectedValue}, is selected', - values: { selectedValue }, - }), - 'euiSuperUpdateButton.cannotUpdateTooltip': i18n.translate( - 'core.euiSuperUpdateButton.cannotUpdateTooltip', - { - defaultMessage: 'Cannot update', - description: "Displayed in a tooltip when updates can't happen", - } - ), - 'euiSuperUpdateButton.clickToApplyTooltip': i18n.translate( - 'core.euiSuperUpdateButton.clickToApplyTooltip', - { - defaultMessage: 'Click to apply', - description: "Displayed in a tooltip when there are changes that haven't been applied", - } - ), - 'euiSuperUpdateButton.refreshButtonLabel': i18n.translate( - 'core.euiSuperUpdateButton.refreshButtonLabel', - { - defaultMessage: 'Refresh', - description: 'Displayed in a button that refreshes based on date picked', - } - ), - 'euiSuperUpdateButton.updatingButtonLabel': i18n.translate( - 'core.euiSuperUpdateButton.updatingButtonLabel', - { - defaultMessage: 'Updating', - description: 'Displayed in a button that refreshes when updates are happening', - } - ), - 'euiSuperUpdateButton.updateButtonLabel': i18n.translate( - 'core.euiSuperUpdateButton.updateButtonLabel', - { - defaultMessage: 'Update', - description: 'Displayed in a button that updates based on date picked', - } - ), - 'euiTablePagination.rowsPerPage': i18n.translate('core.euiTablePagination.rowsPerPage', { - defaultMessage: 'Rows per page', - description: 'Displayed in a button that toggles a table pagination menu', - }), - 'euiTablePagination.rowsPerPageOption': ({ rowsPerPage }: EuiValues) => - i18n.translate('core.euiTablePagination.rowsPerPageOption', { - defaultMessage: '{rowsPerPage} rows', - description: 'Displayed in a button that toggles the number of visible rows', - values: { rowsPerPage }, - }), - 'euiTableSortMobile.sorting': i18n.translate('core.euiTableSortMobile.sorting', { - defaultMessage: 'Sorting', - description: 'Displayed in a button that toggles a table sorting menu', - }), - 'euiToast.dismissToast': i18n.translate('core.euiToast.dismissToast', { - defaultMessage: 'Dismiss toast', - }), - 'euiToast.newNotification': i18n.translate('core.euiToast.newNotification', { - defaultMessage: 'A new notification appears', - }), - 'euiToast.notification': i18n.translate('core.euiToast.notification', { - defaultMessage: 'Notification', - description: 'ARIA label on an element containing a notification', - }), - 'euiTreeView.ariaLabel': ({ nodeLabel, ariaLabel }: EuiValues) => - i18n.translate('core.euiTreeView.ariaLabel', { - defaultMessage: '{nodeLabel} child of {ariaLabel}', - values: { nodeLabel, ariaLabel }, - }), - 'euiTreeView.listNavigationInstructions': i18n.translate( - 'core.euiTreeView.listNavigationInstructions', - { - defaultMessage: 'You can quickly navigate this list using arrow keys.', - } - ), + ...euiContextMapping, }; - return { Context: function I18nContext({ children }) { return ( From c4e4da377d21b4060fcf6917c87103f05d933f76 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 10 Dec 2019 12:21:57 +0100 Subject: [PATCH 35/36] [Discover] Move State to inner angular (#52369) --- .../core_plugins/kibana/public/discover/angular/discover.js | 4 +--- .../core_plugins/kibana/public/discover/get_inner_angular.ts | 5 +++++ .../kibana/public/discover/helpers/build_services.ts | 5 ----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ed233c08e8d492..ce1419cd637b93 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -65,7 +65,6 @@ const { data, docTitle, filterManager, - State, share, timefilter, toastNotifications, @@ -121,7 +120,7 @@ app.config($routeProvider => { template: indexTemplate, reloadOnSearch: false, resolve: { - savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope) { + savedObjects: function (redirectWhenMissing, $route, kbnUrl, Promise, $rootScope, State) { const indexPatterns = getServices().indexPatterns; const savedSearchId = $route.current.params.id; return ensureDefaultIndexPattern(core, getServices().data, $rootScope, kbnUrl).then(() => { @@ -137,7 +136,6 @@ app.config($routeProvider => { * @type {State} */ const state = new State('_a', {}); - const id = getIndexPatternId(state.index, indexPatternList, uiSettings.get('defaultIndex')); state.destroy(); return Promise.props({ diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index 275dfa073fecdf..f6982e13d7d039 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -25,6 +25,8 @@ import 'ui/angular-bootstrap'; import { IPrivate } from 'ui/private'; import { EuiIcon } from '@elastic/eui'; // @ts-ignore +import { StateProvider } from 'ui/state_management/state'; +// @ts-ignore import { EventsProvider } from 'ui/events'; import { PersistedState } from 'ui/persisted_state'; // @ts-ignore @@ -277,6 +279,9 @@ function createLocalAppStateModule() { }) .service('getAppState', function(Private: any) { return Private(AppStateProvider).getAppState; + }) + .service('State', function(Private: any) { + return Private(StateProvider); }); } diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts index a7f849704b5b26..b72bd27a31cf97 100644 --- a/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/build_services.ts @@ -29,8 +29,6 @@ import chromeLegacy from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { FilterManager, TimefilterContract, IndexPatternsContract } from 'src/plugins/data/public'; // @ts-ignore -import { StateProvider } from 'ui/state_management/state'; -// @ts-ignore import { createSavedSearchesService } from '../saved_searches/saved_searches'; // @ts-ignore import { createSavedSearchFactory } from '../saved_searches/_saved_search'; @@ -57,7 +55,6 @@ export interface DiscoverServices { // legacy getSavedSearchById: (id: string) => Promise; getSavedSearchUrlById: (id: string) => Promise; - State: unknown; uiSettings: IUiSettingsClient; } @@ -65,13 +62,11 @@ export async function buildGlobalAngularServices() { const injector = await chromeLegacy.dangerouslyGetActiveInjector(); const Private = injector.get('Private'); const kbnUrl = injector.get('kbnUrl'); - const State = Private(StateProvider); const SavedSearchFactory = createSavedSearchFactory(Private); const service = createSavedSearchesService(Private, SavedSearchFactory, kbnUrl, chromeLegacy); return { getSavedSearchById: async (id: string) => service.get(id), getSavedSearchUrlById: async (id: string) => service.urlFor(id), - State, }; } From f6c44df066360bc24a802d034e2886faba02d739 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 10 Dec 2019 12:37:57 +0100 Subject: [PATCH 36/36] [Uptime] Feature/expandable row in details ping list (#51890) * update columns * update expand row in ping list --- .../__tests__/__snapshots__/monitor_list.test.tsx.snap | 4 ++-- .../__snapshots__/monitor_list_pagination.test.tsx.snap | 4 ++-- .../components/functional/monitor_list/monitor_list.tsx | 2 +- .../__tests__/__snapshots__/ping_list.test.tsx.snap | 6 ++++-- .../public/components/functional/ping_list/ping_list.tsx | 6 ++++-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index 3bf1d68590ec04..0c6acb8d9f46e1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -67,7 +67,7 @@ exports[`MonitorList component renders a no items message when no data is provid "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } @@ -174,7 +174,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap index 91e7b2c07070c1..b7c8ddbc51b276 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap @@ -67,7 +67,7 @@ exports[`MonitorList component renders a no items message when no data is provid "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } @@ -174,7 +174,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` "name": "", "render": [Function], "sortable": true, - "width": "40px", + "width": "24px", }, ] } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx index 7b3b636d99b38d..40a51f7c978e69 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx @@ -208,7 +208,7 @@ export const MonitorListComponent = (props: Props) => { field: 'monitor_id', name: '', sortable: true, - width: '40px', + width: '24px', isExpander: true, render: (id: string) => { return ( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap index 5f60ce38500c80..a2e31a0f33d39f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -145,7 +145,7 @@ exports[`PingList component renders sorted list without errors 1`] = ` "render": [Function], }, Object { - "align": "right", + "align": "center", "field": "http.response.status_code", "name": "Response code", "render": [Function], @@ -154,10 +154,12 @@ exports[`PingList component renders sorted list without errors 1`] = ` "align": "right", "isExpander": true, "render": [Function], - "width": "40px", + "width": "24px", }, ] } + hasActions={true} + isExpandable={true} itemId="id" itemIdToExpandedRowMap={Object {}} items={ diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 0a97b596a7a71e..b3c1de37c13406 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -190,7 +190,7 @@ export const PingListComponent = ({ if (hasStatus) { columns.push({ field: 'http.response.status_code', - align: 'right', + align: 'center', name: i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { defaultMessage: 'Response code', }), @@ -200,7 +200,7 @@ export const PingListComponent = ({ columns.push({ align: 'right', - width: '40px', + width: '24px', isExpander: true, render: (item: Ping) => (